dubbo

dubbo 客户端调用流程

2026-01-293 min read手写RPC框架
Dubbo

前面我们学习了服务端如何启动暴露一个外部服务,本文主要学习客户端如何通过代理方式访问客户端请求

一、启动一个客户端Consumer

1. 定义一个接口

注意这里其实是引用的前文中的接口。生产中是服务提供方打一个jar包给客户端用。

java
1public interface UserService { 2 void say(String message); 3}

2. 生成本地服务

java
1 @Test 2 public void consumerTest() { 3 // 当前应用配置 4 ApplicationConfig application = new ApplicationConfig(); 5 application.setName("consumerTest"); 6 7 // 连接注册中心配置 8 RegistryConfig registry = new RegistryConfig(); 9 registry.setAddress("zookeeper://127.0.0.1:2181"); 10 11 // 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接 12 // 引用远程服务 13 ReferenceConfig<UserService> reference = new ReferenceConfig<UserService>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏 14 reference.setApplication(application); 15 reference.setRegistry(registry); // 多个注册中心可以用setRegistries() 16 reference.setInterface(UserService.class); 17 reference.setVersion("1.0.0"); 18 UserService userService = reference.get(); 19 userService.say("hello"); 20 }

3. 原理分析

首先客户端只有接口的,那么可以根据这个接口生成一个代理。而代理对象中逻辑就是,从zk中找到服务端地址。 然后通过netty客户端去请求服务端的数据。然后返回

二、源码分析

带着我们猜测的逻辑一起来看下ReferenceConfig的实现原理。

java
1 public synchronized T get() { 2 if (destroyed){ 3 throw new IllegalStateException("Already destroyed!"); 4 } 5 if (ref == null) { 6 //逻辑就在init里面 7 init(); 8 } 9 return ref; 10 }

init先做写检查信息,如这个方法是否存在接口中 createProxy#loadRegistries

1. 集群容错策略

可以看到一共有9中策略。

当时服务端是多个的时候,才会生成集群策略。另外既然是集群就要选择到底使用哪个来执行。这就是 负载均衡或者说叫路由策略。

LoadBalance负载均衡

  • directory中获取所有的invoker
  • 如果有多个invoker就去看配置的负载均衡策略
  • 根据负载均衡策略找到一个Inoker
java
1public abstract class AbstractClusterInvoker<T> implements Invoker<T> { 2 public Result invoke(final Invocation invocation) throws RpcException { 3 4 checkWheatherDestoried(); 5 6 LoadBalance loadbalance; 7 //获取所有的invoker 8 List<Invoker<T>> invokers = list(invocation); 9 //如果有多个invoker就去看配置的负载均衡策略 10 if (invokers != null && invokers.size() > 0) { 11 loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl() 12 .getMethodParameter(invocation.getMethodName(),Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE)); 13 } else { 14 loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE); 15 } 16 RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); 17 //根据策略选一个 18 return doInvoke(invocation, invokers, loadbalance); 19 } 20 21 protected List<Invoker<T>> list(Invocation invocation) throws RpcException { 22 List<Invoker<T>> invokers = directory.list(invocation); 23 return invokers; 24 } 25}

2. invoker生成代理对象

代理的知识点不用说了。

3. 客户端的invoker逻辑

Protocol#refer

主要看DubboProtocol的逻辑

java
1 public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException { 2 // create rpc invoker. 3 DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers); 4 invokers.add(invoker); 5 return invoker; 6 }

DubboInvoker

底层调用netty通信api发送数据到客户端。然后读取数据。

java
1 2客户端doInvoke时候会生成ExchangeClient就是NettyClient3public class DubboInvoker<T> extends AbstractInvoker<T> { 4 5 @Override 6 protected Result doInvoke(final Invocation invocation) throws Throwable { 7 RpcInvocation inv = (RpcInvocation) invocation; 8 final String methodName = RpcUtils.getMethodName(invocation); 9 inv.setAttachment(Constants.PATH_KEY, getUrl().getPath()); 10 inv.setAttachment(Constants.VERSION_KEY, version); 11 12 ExchangeClient currentClient; 13 if (clients.length == 1) { 14 currentClient = clients[0]; 15 } else { 16 currentClient = clients[index.getAndIncrement() % clients.length]; 17 } 18 try { 19 boolean isAsync = RpcUtils.isAsync(getUrl(), invocation); 20 boolean isOneway = RpcUtils.isOneway(getUrl(), invocation); 21 int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY,Constants.DEFAULT_TIMEOUT); 22 if (isOneway) { 23 boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false); 24 currentClient.send(inv, isSent); 25 RpcContext.getContext().setFuture(null); 26 return new RpcResult(); 27 } else if (isAsync) { 28 ResponseFuture future = currentClient.request(inv, timeout) ; 29 RpcContext.getContext().setFuture(new FutureAdapter<Object>(future)); 30 return new RpcResult(); 31 } else { 32 RpcContext.getContext().setFuture(null); 33 return (Result) currentClient.request(inv, timeout).get(); 34 } 35 } catch (TimeoutException e) { 36 throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e); 37 } catch (RemotingException e) { 38 throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e); 39 } 40 } 41 42 @Override 43 public boolean isAvailable() { 44 if (!super.isAvailable()) 45 return false; 46 for (ExchangeClient client : clients){ 47 if (client.isConnected() && !client.hasAttribute(Constants.CHANNEL_ATTRIBUTE_READONLY_KEY)){ 48 //cannot write == not Available ? 49 return true ; 50 } 51 } 52 return false; 53 } 54 55 56}

三、总结

在前文的基础上,客户端的代码算是比较简单的。主要是集群容错和负载均衡、路由。

主要是利用代理来实现的。

最后求关注,求订阅,谢谢你的阅读!

下一篇会讲,dubbo如何与Spring进行整合。