2023-09-18
原文作者:carl.zhao 原文地址: https://blog.csdn.net/u012410733/article/details/79562117

在上一篇文章我们讲解了一下 dubbo 远程服务暴露过程中通过 Netty 进行 Socket 服务暴露。使得远程客户端可以访问这个暴露的服务,这个只是解决了访问之前点到点的服务调用。对于分步式环境当中,越来越多的服务我们如何管理并且治理这些服务是一个问题。因此 dubbo 引入了注册中心这个概念,把服务暴露、服务调用的信息保存到注册中心上面。并且还可以订阅注册中心,实现服务自动发现。因为 dubbo 远程暴露里面的过程还是比较复杂的,所以我就分为三个文章来讲解 dubbo 的远程暴露:

  • dubbo 远程暴露 – Netty 暴露服务
  • dubbo 远程暴露 – Zookeeper 连接
  • dubbo 远程暴露 – Zookeeper 注册 & 订阅

dubbo 支持以下几种注册中心

注册中心 成熟度 优点 问题 建议
Zookeeper注册中心 Stable 支持基于网络的集群方式,有广泛周边开源产品,建议使用dubbo-2.3.3以上版本(推荐使用) 依赖于Zookeeper的稳定性 可用于生产环境
Redis注册中心 Stable 支持基于客户端双写的集群方式,性能高 要求服务器时间同步,用于检查心跳过期脏数据 可用于生产环境
Multicast注册中心 Tested 去中心化,不需要安装注册中心 依赖于网络拓普和路由,跨机房有风险 小规模应用或开发测试环境
Simple注册中心 Tested Dogfooding,注册中心本身也是一个标准的RPC服务 没有集群支持,可能单点故障 试用

官方推荐使用 zookeeper 为注册中心。在我们分析一下 dubbo 中是如何集成 zookeeper 之前,我们先来回顾一下 dubbo 服务暴露里面的主要步骤。

1、RegistryProtocol#export

下面就是 dubbo 暴露的核心步骤的代码,可能由于版本的原因(下面的代码基于 2.6.1)代码会有所差异但是核心思想不变。

        public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
            //export invoker
            final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
    
            URL registryUrl = getRegistryUrl(originInvoker);
    
            //registry provider
            final Registry registry = getRegistry(originInvoker);
            final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
    
            //to judge to delay publish whether or not
            boolean register = registedProviderUrl.getParameter("register", true);
    
            ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
    
            if (register) {
                register(registryUrl, registedProviderUrl);
                ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
            }
    
            // Subscribe the override data
            // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover.
            final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
            final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
            overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
            registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
            //Ensure that a new exporter instance is returned every time export
            return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
        }

之前我们分析了第一步:ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);dubbo 基于 socket 的本地暴露提供服务给远程客户端调用。下面我们就来分析服务远程暴露的注册服务到 zookeeper 这个注册中心上面来实现 高可用 的。

2、RegistryProtocol#getRegistry

1、把 URL 里面的 protocol 设置成 <dubbo:registry address="zookeeper://127.0.0.1:2181"里面设置的 zookeeper。
2、通过 RegistryFactory 的 SPI 接口 RegistryFactory$Adaptive根据 URL 里面的 protocol 获取 zookeeper 的注册工厂调用 getRegistry获取 zookeeper 注册中心 – ZookeeperRegistry。

以下是由 dubbo 字节码服务生成的 RegistryFactory$Adaptive

    public class RegistryFactory$Adaptive implements com.alibaba.dubbo.registry.RegistryFactory {
       public com.alibaba.dubbo.registry.Registry getRegistry(com.alibaba.dubbo.common.URL arg0) {
           if (arg0 == null) throw new IllegalArgumentException("url == null");
           com.alibaba.dubbo.common.URL url = arg0;
           String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
           if (extName == null)
               throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.registry.RegistryFactory) name from url(" + url.toString() + ") use keys([protocol])");
           com.alibaba.dubbo.registry.RegistryFactory extension = (com.alibaba.dubbo.registry.RegistryFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.registry.RegistryFactory.class).getExtension(extName);
           return extension.getRegistry(arg0);
       }
    }

3、AbstractRegistryFactory.getRegistry

1、URL 添加 参数interfacecom.alibaba.dubbo.registry.RegistryService,并去掉 export 参数。
2、ZookeeperRegistryFactory#createRegistry创建 ZookeeperRegistry 实例
3、AbstractRegistry#init()中调用loadProperties(),在以下目录中保存注册信息(以window为例)。

    C:\Users\Carl\.dubbo\dubbo-registry-127.0.0.1.cache

4、FailbackRegistry#init()中故障回复注册类中创建线程池 ScheduledExecutorService 检测并连接注册中心,如果失败就就调用 retry()进行重连,高可用。
5、zookeeperTransporter#connect()由于 ZookeeperTransporter 是一个 @SPI 接口并且 @Adaptive,所以会生成一个 ZookeeperTransporter$Adaptive,并且是由RegistryFactory这个 SPI 接口创建的时候通过 SPI 依赖注入创建 ZookeeperRegistryFactory 对象的时候依赖注入的。然后通过ZookeeperRegistryFactory#createRegistry()把这个接口传入的。而且因为 我的这个版本使用的是 dubbo-2.6.1 版本所以默认使用的是 Curator 这个 Zookeeper 客户端,之前低版本默认使用的是 Zkclient 这个默认客户端。如果大家对 dubbo 里面的 SPI 机制不太了解可以看之前的 blog – 2.dubbo源码分析 之 内核SPI实现

202309182343300641.png

不知道大家细心观察没有:Curator 和 Zkclient 在连接 zookeeper 的时候代码风格不太一样。而 dubbo 在进行 zookeeper 连接的时候通过 ZkClientWrapper这个包装类使得 Zkclient 与 Curator 的代码风格一致。

1、Curator – CuratorZookeeperClient#init()

    // 构造连接参数
    CuratorFramework client = builder.build();
    // 进行连接操作
    client.start();

2、Zkclient – ZkclientZookeeperClient#init()

    // 构造连接参数
    ZkClientWrapper client =  = new ZkClientWrapper(url.getBackupAddress(), 30000);
    // 进行连接操作
    client.start();

这个就是 dubbo 平等对待第三方框架,而且把 zk 的两种不同的客户端的代码风格统一了起来。

阅读全文