Dubbo原理解析,彻底搞懂dubbo (下)

服务发现

服务发现流程

整体duubo的服务消费原理

Dubbo 框架做服务消费也分为两大部分 , 第一步通过持有远程服务实例生成Invoker,这个Invoker 在客户端是核心的远程代理对象 。 第二步会把Invoker 通过动态代理转换成实现用户接口的动态代理引用 。

服务消费方引用服务的蓝色初始化链,时序图

源码分析应用

引用入口:ReferenceBean 的getObject 方法,该方法定义在Spring 的FactoryBean 接口中,ReferenceBean 实现了这个方法。

public Object getObject() throws Exception {
   return get();
}
public synchronized T get() {
   // 检测 ref 是否为空,为空则通过 init 方法创建
   if (ref == null) {
       // init 方法主要用于处理配置,以及调用 createProxy 生成代理类
       init();
   }
   return ref;
}

Dubbo 提供了丰富的配置,用于调整和优化框架行为,性能等。Dubbo 在引用或导出服务时,首先会对这些配置进行检查和处理,以保证配置的正确性。

private void init() {
   // 创建代理类
   ref = createProxy(map);
}

此方法代码很长,主要完成的配置加载,检查,以及创建引用的代理对象。这里要从createProxy 开始看起。从字面意思上来看,createProxy 似乎只是用于创建代理对象的。但实际上并非如此,该方法还会调用其他方法构建以及合并Invoker 实例。具体细节如下。

private T createProxy(Map<String, String> map) {
   URL tmpUrl = new URL("temp", "localhost", 0, map);
...........
isDvmRefer = InjvmProtocol . getlnjvmProtocol( ) . islnjvmRefer(tmpUrl)
   // 本地引用略
   if (isJvmRefer) {
   } else {
       // 点对点调用略
       if (url != null && url.length() > 0) {
           
       } else {
           // 加载注册中心 url
           List<URL> us = loadRegistries(false);
           if (us != null && !us.isEmpty()) {
               for (URL u : us) {
                   URL monitorUrl = loadMonitor(u);
                   if (monitorUrl != null) {
                       map.put(Constants.MONITOR_KEY,
URL.encode(monitorUrl.toFullString()));
                   }
                   // 添加 refer 参数到 url 中,并将 url 添加到 urls 中
                   urls.add(u.addParameterAndEncoded(Constants.REFER_KEY,
StringUtils.toQueryString(map)));
               }
           }
       }
       // 单个注册中心或服务提供者(服务直连,下同)
       if (urls.size() == 1) {
           // 调用 RegistryProtocol 的 refer 构建 Invoker 实例
           invoker = refprotocol.refer(interfaceClass, urls.get(0));
       // 多个注册中心或多个服务提供者,或者两者混合
       } else {
           List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
           URL registryURL = null;
           // 获取所有的 Invoker
           for (URL url : urls) {
               // 通过 refprotocol 调用 refer 构建 Invoker,refprotocol 会在运行时
               // 根据 url 协议头加载指定的 Protocol 实例,并调用实例的 refer 方法
               invokers.add(refprotocol.refer(interfaceClass, url));
               if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                   registryURL = url;
               }
           }
           if (registryURL != null) {
               // 如果注册中心链接不为空,则将使用 AvailableCluster
               URL u = registryURL.addParameter(Constants.CLUSTER_KEY,
AvailableCluster.NAME);
               // 创建 StaticDirectory 实例,并由 Cluster 对多个 Invoker 进行合并
               invoker = cluster.join(new StaticDirectory(u, invokers));
           } else {
               invoker = cluster.join(new StaticDirectory(invokers));
           }
       }
   }
    //省略无关代码...
    // 生成代理类
   return (T) proxyFactory.getProxy(invoker);
}   

折叠

上面代码很多,不过逻辑比较清晰。
1、如果是本地调用,直接jvm 协议从内存中获取实例
2、如果只有一个注册中心,直接通过Protocol 自适应拓展类构建Invoker 实例接口
3、如果有多个注册中心,此时先根据url 构建Invoker。然后再通过Cluster 合并多个Invoker,最后调用ProxyFactory 生成代理类

创建客户端

在服务消费方,Invoker 用于执行远程调用。Invoker 是由Protocol 实现类构建而来。Protocol 实现类有很多,这里分析DubboProtocol

public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
   optimizeSerialization(url);
   // 创建 DubboInvoker
   DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url,
getClients(url), invokers);
   invokers.add(invoker);
   return invoker;
}

上面方法看起来比较简单,创建一个DubboInvoker。通过构造方法传入远程调用的client对象。默认情况下,Dubbo 使用NettyClient 进行通信。接下来,我们简单看一下getClients 方法的逻辑。

private ExchangeClient[] getClients(URL url) {
   // 是否共享连接
   boolean service_share_connect = false;
// 获取连接数,默认为0,表示未配置
   int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
   // 如果未配置 connections,则共享连接
   if (connections == 0) {
       service_share_connect = true;
       connections = 1;
   }
   ExchangeClient[] clients = new ExchangeClient[connections];
   for (int i = 0; i < clients.length; i++) {
       if (service_share_connect) {
           // 获取共享客户端
           clients[i] = getSharedClient(url);
       } else {
           // 初始化新的客户端
           clients[i] = initClient(url);
       }
   }
   return clients;
}

这里根据connections 数量决定是获取共享客户端还是创建新的客户端实例,getSharedClient 方法中也会调用initClient 方法,因此下面我们一起看一下这个方法。

private ExchangeClient initClient(URL url) {
   // 获取客户端类型,默认为 netty
   String str = url.getParameter(Constants.CLIENT_KEY,
url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
   //省略无关代码
   ExchangeClient client;
   try {
       // 获取 lazy 配置,并根据配置值决定创建的客户端类型
       if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
           // 创建懒加载 ExchangeClient 实例
           client = new LazyConnectExchangeClient(url, requestHandler);
       } else {
           // 创建普通 ExchangeClient 实例
           client = Exchangers.connect(url, requestHandler);
       }
   } catch (RemotingException e) {
       throw new RpcException("Fail to create remoting client for service...");
   }
   return client;
}

initClient 方法首先获取用户配置的客户端类型,默认为netty。下面我们分析一下Exchangers 的connect 方法。

public static ExchangeClient connect(URL url, ExchangeHandler handler) throws
RemotingException {
   // 获取 Exchanger 实例,默认为 HeaderExchangeClient
   return getExchanger(url).connect(url, handler);
}

如上,getExchanger 会通过SPI 加载HeaderExchangeClient 实例,这个方法比较简单,大家自己看一下吧。接下来分析HeaderExchangeClient 的实现。

public ExchangeClient connect(URL url, ExchangeHandler handler) throws
RemotingException {
   // 这里包含了多个调用,分别如下:
   // 1. 创建 HeaderExchangeHandler 对象
   // 2. 创建 DecodeHandler 对象
   // 3. 通过 Transporters 构建 Client 实例
   // 4. 创建 HeaderExchangeClient 对象
   return new HeaderExchangeClient(Transporters.connect(url, new
DecodeHandler(new HeaderExchangeHandler(handler))), true);
}

这里的调用比较多,我们这里重点看一下Transporters 的connect 方法。如下:

public static Client connect(URL url, ChannelHandler... handlers) throws
RemotingException {
   if (url == null) {
       throw new IllegalArgumentException("url == null");
       }
   ChannelHandler handler;
   if (handlers == null || handlers.length == 0) {
       handler = new ChannelHandlerAdapter();
   } else if (handlers.length == 1) {
       handler = handlers[0];
   } else {
       // 如果 handler 数量大于1,则创建一个 ChannelHandler 分发器
       handler = new ChannelHandlerDispatcher(handlers);
   }
   
   // 获取 Transporter 自适应拓展类,并调用 connect 方法生成 Client 实例
   return getTransporter().connect(url, handler);
}

如上,getTransporter 方法返回的是自适应拓展类,该类会在运行时根据客户端类型加载指定的Transporter 实现类。若用户未配置客户端类型,则默认加载NettyTransporter,并调用该类的connect 方法。如下:

public Client connect(URL url, ChannelHandler listener) throws RemotingException
{
   // 创建 NettyClient 对象
   return new NettyClient(url, listener);
}

注册

这里就已经创建好了NettyClient对象。关于DubboProtocol 的refer 方法就分析完了。接下来,继续分析RegistryProtocol 的refer 方法逻辑。

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
   // 取 registry 参数值,并将其设置为协议头
   url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY,
Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
   // 获取注册中心实例
   Registry registry = registryFactory.getRegistry(url);
   if (RegistryService.class.equals(type)) {
       return proxyFactory.getInvoker((T) registry, type, url);
   }
   // 将 url 查询字符串转为 Map
   Map<String, String> qs =
StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
   // 获取 group 配置
   String group = qs.get(Constants.GROUP_KEY);
   if (group != null && group.length() > 0) {
       if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
               || "*".equals(group)) {
           // 通过 SPI 加载 MergeableCluster 实例,并调用 doRefer 继续执行服务引用逻辑
           return doRefer(getMergeableCluster(), registry, type, url);
       }
   }
   
   // 调用 doRefer 继续执行服务引用逻辑
   return doRefer(cluster, registry, type, url);
}

上面代码首先为url 设置协议头,然后根据url 参数加载注册中心实例。然后获取group 配置,根据group 配置决定doRefer 第一个参数的类型。这里的重点是doRefer 方法,如下:

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T>
type, URL url) {
   // 创建 RegistryDirectory 实例
   RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
   // 设置注册中心和协议
   directory.setRegistry(registry);
   directory.setProtocol(protocol);
   Map<String, String> parameters = new HashMap<String, String>
(directory.getUrl().getParameters());
   // 生成服务消费者链接
   URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL,
parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
   // 注册服务消费者,在 consumers 目录下新节点
   if (!
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值