Dubbo 消费端引用源码梳理

198 阅读4分钟

Dubbo 消费端引用梳理

服务消息端通过ReferenceConfig get() 方法拿到服务调用代理对象, 从而实现无感的远程调用。你可能有一堆问题

  • 代理对象怎么生成的
  • 默认通信协议是什么
  • 它是如何感知到服务提供者的列表变化
  • .......

先直观看下官网服务消费者消费一个服务的过程示意图

图片

从一个入门例子讲起

ReferenceConfig.get() 方法我们获取到服务远程调用的代理对象。调用具体业务方法时代理对象会无感发起远程调用,并且将调用结果返回。这中间可能涉及集群容错、负载均衡、各种拦截器等等

ReferenceConfig<DemoService> referenceConfig = new ReferenceConfig<>();
referenceConfig.setRegistry(new RegistryConfig("N/A"));

referenceConfig.setApplication(new ApplicationConfig("dubbo"));
referenceConfig.setUrl("dubbo://127.0.0.1:20880");
referenceConfig.setInterface(DemoService.class);
referenceConfig.setVersion("1.0");

DemoService demoService = referenceConfig.get();
String sayHello = demoService.sayHello("hello ");

消费端引用源码处理

图片

首次调用get方法时会进行初始化,记录代理对象ref ,后续请求重复使用这个ref对象

初始化过程会判断泛化调用,参数组装等等。核心代码 根据请求参数创建代理对象

ref = createProxy(map);

创建代理对象代码梳理

createProxy 核心代码全貌(以下仅仅摘取核心代码)

 private T createProxy(Map<String, String> map) {
        if (shouldJvmRefer(map)) {
            URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
            invoker = REF_PROTOCOL.refer(interfaceClass, url);
            if (logger.isInfoEnabled()) {
                logger.info("Using injvm service " + interfaceClass.getName());
            }
        } else {


            if (urls.size() == 1) {
                invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
            } else {
                List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
                URL registryURL = null;
                for (URL url : urls) {
                    invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
                    if (UrlUtils.isRegistry(url)) {
                        registryURL = url; // use last registry url
                    }
                }
                if (registryURL != null) { // registry url is available
                    // for multi-subscription scenario, use 'zone-aware' policy by default
                    String cluster = registryURL.getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME);
                    // The invoker wrap sequence would be: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker
                    invoker = Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers));
                } else { // not a registry url, must be direct invoke.
                    String cluster = CollectionUtils.isNotEmpty(invokers)
                            ? (invokers.get(0).getUrl() != null ? invokers.get(0).getUrl().getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME) : Cluster.DEFAULT)
                            : Cluster.DEFAULT;
                    invoker = Cluster.getCluster(cluster).join(new StaticDirectory(invokers));
                }
            }
        }

        // create service proxy
        return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
    }

同一jvm 引用

当服务提供方和调用方是同一jvm进程时,这时不需要发起请求直接调用即可。

    if (shouldJvmRefer(map)) {
            URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
            invoker = REF_PROTOCOL.refer(interfaceClass, url);
            if (logger.isInfoEnabled()) {
                logger.info("Using injvm service " + interfaceClass.getName());
            }
        }
   private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

如果是同一jvm 引用,则new URL对象,其protocol LOCAL_PROTOCOL。

invoker = REF_PROTOCOL.refer(interfaceClass, url); 得到invoker执行对象。

REF_PROTOCOL 由于 dubbo SPI Adaptive机制,会自适应选择InjvmProtocol实现,此时我们的invoker 实际上为 InjvmInvoker ,InjvmInvoker 直接从从对应的Exporter中拿到对应服务提供者的Invoker,执行具体的业务逻辑,这里并不会发起远程调用。

图片

远程调用

如果不是同一jvm 就要需要远程调用,服务提供者信息从注册中心中获取,dubbo 允许多注册中心,对于每个注册中心url 都会转换为一个Invoker。

单注册中心情况

直接通过invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)); 拿到单个invoker 对象,REF_PROTOCOL 默认是DubboProtocol

多注册中心情况

多注册中心, 一个注册中心会得到一个invoker 对象,会得到invokers

Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers))

将多个invokers"伪装"成一个invoker ,同时包装了容错策略,负载均衡

图片

RegistryProtocol

回顾下上篇文章服务发布流程,Dubbo Wrap机制,上面protocol.refer 还会经过 RegistryProtocol 进行增强

图片

directory.subscribe(toSubscribeUrl(subscribeUrl));订阅注册中心变化, RegistryDirectory感知注册中心变化 每一个提供者转换为一个invoker (同一服务,可以有多台服务器分布式部署,每个ip port都会对应一个invoker)。同样通过  cluster.join(directory); 进行容错策略,负载均衡包装。

invoker 已经各种Wrap 包装了,最底层的是DubboInvoker(与Protocol对应)

DubboInvoker

图片

DubboInvoker中包含List invokers属性,这里invokers就是所有服务提供者的列表, 实际调用是 cluster包含负载均衡,选择一个进行具体机器的服务提供者进行调用。

以FailfastClusterInvoker 为例,select 方法根据负载均衡算法选择一个实际的服务提供者进行调用。

图片

再看看DubboInvoker doInvoke 方法,ExchangeClient 是通信的关键。

图片

ExchangeClient

再看看ExchangeClient是哪里来的,回到DubboProtocol

DubboInvoker<T>(serviceType, url, getClients(url), invokers)

再看看getClients(url) 方法,最终会调用initClient(URL url),

追踪进去根据url 建立Client 指定了 requestHandle (和上篇文章服务提供者一样),requestHandle 处理 对应请求处理 (包含TCP连接相关回调处理)reply、received、connected、disconnected等方法。

    private ExchangeClient initClient(URL url) {
      //省略一些无关代码
      ExchangeClient client;
      try {
          // connection should be lazy
          if (url.getParameter(LAZY_CONNECT_KEYfalse)) {
              client = new LazyConnectExchangeClient(url, requestHandler);

          } else {
              client = Exchangers.connect(url, requestHandler);
          }

      } catch (RemotingException e) {
          throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
      }

      return client;
  }

图片

图片

ref 对象

断点查看最终ref对象是什么,真实的Invoker确实被各种包装。从扩展性角度Dubbo 高度扩展性设计,对于源码学习会造成一些学习上障碍。

图片

图片

总结

本文梳理Dubbo消费端获取引用对象,无感发起远程调用流程,当然限于篇幅,本文只讨论主流程, 一些集群容错、负载均衡等等没有涉及。