• SpringBoot整合Dubbo3.x关于curator和zookeeper版本选型的思考


    一、Dubbo2 or Dubbo3?

    我给出的观点是选择 Dubbo3,原因有二:

    1. 在 Dubbo 3.0 版本向下兼容老版本 Dubbo 2.5、2.6、2.7;
    2. Dubbo 3.0 的带来了许多的新特性,用户可以按需进行升级;

    参考自Apache官方文档 《Dubbo 3.x 升级与兼容性指南》

    在 SpringBoot 整合 Dubbo 时,如果引用依赖 dubbo-spring-boot-starter ,会自动依赖 dubbo,且版本号一致:

    <dependency>
      <groupId>org.apache.dubbo</groupId>
      <artifactId>dubbo-spring-boot-starter</artifactId>
      <version>3.0.1</version>
    </dependency>
    

    当然,如果选择直接引用 Dubbo :

    <dependency>
      <groupId>org.apache.dubbo</groupId>
      <artifactId>dubbo</artifactId>
      <version>3.0.1</version>
    </dependency>
    

    二、Zookeeper&curator or Zookeeper&zkclient

    Dubbo 常用的注册中心有 Nacos、ZooKeeper、Multicast、Redis、Simple。本文主要讨论 ZooKeeper 作为 Dubbo 的注册中心时,版本的选择。

    Dubbo 支持 zkclient 和 curator 两种 Zookeeper 客户端实现。
    注意:在2.7.x的版本中已经移除了zkclient的实现,如果要使用zkclient客户端,需要自行拓展

    参考自 Apache Dubbo 官网《Zookeeper 注册中心参考手册》

    curator 比 zkclient 更加通用,还有许多现成好用的API,且 curator 有相对完善的文档。就像 Curator 的口号一样:

    所以不考虑 zkclient,选用 curator。

    三、Curator 和 Zookeeper 的兼容问题

    ZooKeeper 3.4.x 已经走到头了。因此,最新版本的Curator取消了对它的支持。如果你想使用 Zookeeper 3.4.x 您应该锁定到 Curator 版本4.2.x。
    Curator 4.2.x 以软兼容模式支持 ZooKeeper 3.4.x 集成。要使用此模式,在向依赖关系管理工具添加Curator时,必须排除ZooKeeper。

    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-recipes</artifactId>
      <version>4.2.0</version>
      <exclusions>
        <exclusion>
          <groupId>org.apache.zookeeper</groupId>
          <artifactId>zookeeper</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    

    翻译自 《ZooKeeper Version 3.4.x Compatibility》

    3.1 兼容老服务器ZooKeeper3.4.x的版本选型

    如果 ZooKeeper 服务器选型为 3.4.x 时,采用以下选型(主要针对 ZooKeeper 服务器集群使用较低版本的情况):
    Dubbo 3.0.1 & Curator 4.2.0 & Zookeeper 3.4.x

    <dependencies>
      <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>3.0.1</version>
      </dependency>
      <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>4.2.0</version>
        <exclusions>
          <exclusion>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
          </exclusion>
        </exclusions>
      </dependency>
      <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-x-discovery</artifactId>
        <version>4.2.0</version>
      </dependency>
      <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.4.10</version>
      </dependency>
    </dependencies>
    

    一张表来说明需要要到的Curator相关包:

    GroupID ArtifactID 描述
    org.apache.curator curator-recipes All of the recipes. 该 artifact 依赖 curator-framework 和 curator-client
    org.apache.curator curator-framework Curator框架 high-level API。建立在 curator-client 之上。
    org.apache.curator curator-client ZK发行版中ZooKeeper类的替代品。
    org.apache.curator curator-x-discovery 基于Curator框架的服务发现实现

    翻译自Curator官方文档《Maven/Artifact》

    zookeeper 中排除了日志相关的三个包,就可以让 ZooKeeper 的日志框架保持和整体日志框架一致,但是 slf4j-api 是不可缺少的日志门面。
    同样地,排除 curator-client 中的 slf4j-api 包,也是为了让其日志框架和整体日志框架一致。

    3.2 新搭ZooKeeper环境

    针对新搭 ZooKeeper 的情况,可以采用更新的版本:
    Dubbo 3.0.5 & Curator 5.2.0 & Zookeeper 3.6.3 ,选择 ZooKeeper 3.6.3 是因为 curator-client 5.2.0 的其中一个依赖就是 zookeeper 3.6.3
    (Dubbo 3.0.2+ 都可以,但是 Dubbo 3.0.1 有点兼容问题)

    <dependencies>
      <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>3.0.5</version>
      </dependency>
      <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>5.2.0</version>
      </dependency>
      <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-x-discovery</artifactId>
        <version>5.2.0</version>
      </dependency>
    </dependencies>
    

    四、关于选型新版本的一点小问题

    4.1 问题描述和解决方案

    我使用 Docker快速搭建ZooKeeper集群
    但是我在启动Dubbo应用服务时出现以下错误:

    java.lang.NullPointerException: null
    	at org.apache.curator.utils.Compatibility.getHostAddress(Compatibility.java:116) ~[curator-client-5.2.0.jar:na]
    	at org.apache.curator.framework.imps.EnsembleTracker.configToConnectionString(EnsembleTracker.java:185) ~[curator-framework-5.2.0.jar:5.2.0]
    	at org.apache.curator.framework.imps.EnsembleTracker.processConfigData(EnsembleTracker.java:206) ~[curator-framework-5.2.0.jar:5.2.0]
    	at org.apache.curator.framework.imps.EnsembleTracker.access$300(EnsembleTracker.java:50) ~[curator-framework-5.2.0.jar:5.2.0]
    	at org.apache.curator.framework.imps.EnsembleTracker$2.processResult(EnsembleTracker.java:150) ~[curator-framework-5.2.0.jar:5.2.0]
    	at org.apache.curator.framework.imps.CuratorFrameworkImpl.sendToBackgroundCallback(CuratorFrameworkImpl.java:926) [curator-framework-5.2.0.jar:5.2.0]
    	at org.apache.curator.framework.imps.CuratorFrameworkImpl.processBackgroundOperation(CuratorFrameworkImpl.java:683) [curator-framework-5.2.0.jar:5.2.0]
    	at org.apache.curator.framework.imps.WatcherRemovalFacade.processBackgroundOperation(WatcherRemovalFacade.java:152) [curator-framework-5.2.0.jar:5.2.0]
    	at org.apache.curator.framework.imps.GetConfigBuilderImpl$2.processResult(GetConfigBuilderImpl.java:222) [curator-framework-5.2.0.jar:5.2.0]
    	at org.apache.zookeeper.ClientCnxn$EventThread.processEvent(ClientCnxn.java:644) [zookeeper-3.6.3.jar:3.6.3]
    	at org.apache.zookeeper.ClientCnxn$EventThread.run(ClientCnxn.java:563) [zookeeper-3.6.3.jar:3.6.3]
    

    我查了很久的源码,才弄清楚原因。解决方案是在 hosts 中给 zoo1,zoo2,zoo3 配置 ip,例如在 hosts 文件末尾追加

    10.24.99.61 zoo1
    10.24.99.61 zoo2
    10.24.99.61 zoo3
    

    10.24.99.61 则是我用 ipconfig 命令(Windows系统)查询到一个本机Ipv4地址。

    4.2 源码解析

    首先,如果你用的 Curator 5.2.0,那么使用的是 Curator5ZookeeperClient ,如果你用的是 Curator 4.2.0,使用的则是 CuratorZookeeperClient,截取了部分核心代码:

    把断点放在 client.start(),并继续跟踪代码,代码会进入 CuratorFrameworkImplstart() 方法,其中有一段代码

    if ( ensembleTracker != null )
    {
        ensembleTracker.start();
    }
    

    这段代码,如果使用 ZooKeeper 3.4.x 时,ensembleTracker等于null;相反地,使用 ZooKeeper 3.6.3 则不为 null,原因是 CuratorFrameworkImpl 构造函数中的这段代码:

    ensembleTracker = zk34CompatibilityMode ? null : new EnsembleTracker(this, builder.getEnsembleProvider());
    

    至于 zk34CompatibilityMode 则是根据 org.apache.curator.utils.Compatibility 判断的(这里就不展开了,原来就是反射查看 org.apache.zookeeper.admin.ZooKeeperAdmin 是否能找到来判断的)。

    然后,再来看 EnsembleTrackerstart() 方法

    // org.apache.curator.framework.imps.EnsembleTracker
    public void start() throws Exception
    {
      Preconditions.checkState(state.compareAndSet(State.LATENT, State.STARTED), "Cannot be started more than once");
      client.getConnectionStateListenable().addListener(connectionStateListener);
      reset();
    }
    

    继续跟踪 EnsembleTrackerreset() 方法

    private void reset() throws Exception
    {
      if ( (client.getState() == CuratorFrameworkState.STARTED) && (state.get() == State.STARTED) )
      {
        BackgroundCallback backgroundCallback = new BackgroundCallback()
        {
          @Override
          public void processResult(CuratorFramework client, CuratorEvent event) throws Exception
          {
            outstanding.decrementAndGet();
            if ( (event.getType() == CuratorEventType.GET_CONFIG) && (event.getResultCode() == KeeperException.Code.OK.intValue()) )
            {
              processConfigData(event.getData()); // 这里是重点
            }
          }
        };
        outstanding.incrementAndGet();
        try
        {
          client.getConfig().usingWatcher(this).inBackground(backgroundCallback).forEnsemble();
          outstanding.incrementAndGet();  // finally block will decrement
        }
        finally
        {
          outstanding.decrementAndGet();
        }
      }
    }
    

    断点放在 processConfigData 上,并进入该方法,继续跟踪:

    private void processConfigData(byte[] data) throws Exception
    {
      Properties properties = new Properties();
      properties.load(new ByteArrayInputStream(data));
      log.info("New config event received: {}", properties);
      if (!properties.isEmpty())
      {
        QuorumMaj newConfig = new QuorumMaj(properties); // 本质产生异常的方法
        String connectionString = configToConnectionString(newConfig); // 抛出异常的方法
        if (connectionString.trim().length() > 0)
        {
          currentConfig.set(newConfig);
          ensembleProvider.setConnectionString(connectionString);
        }
        else
        {
          log.debug("Invalid config event received: {}", properties);
        }
      }
      else
      {
        log.debug("Ignoring new config as it is empty");
      }
    }
    

    抛出异常的是 configToConnectionString 调用 hostAddress = Compatibility.getHostAddress(server);

    因为 addr 等于 null 才导致的错误,但是这不是“第一案发现场”,真正发生问题是在 QuorumMaj newConfig = new QuorumMaj(properties); 之中:

    server.1=zoo1:2888:3888:participant;0.0.0.0:2181 server.2=zoo2:2888:3888:participant;0.0.0.0:2182 server.3=zoo3:2888:3888:participant;0.0.0.0:2183 逐一解析为 QuorumServer 对象,其中调用 InetSocketAddress 构造函数时:

    当 zoo1 找不到对应的 ip 时,addr 就会为 null。这就是问题的原因。所以配置一下本地 hosts 就能搞定了。

  • 相关阅读:
    消息队列中间件的技术选型分析
    数据库和缓存一致性的问题
    《RocketMQ 安装和使用》
    RocketMQ原理讲解系列文章
    阿里巴巴开源项目
    RocketMQ与Kafka对比(18项差异)
    对象初始化
    pytest_05_fixture之conftest.py
    pytest_04_测试用例setup和teardown
    Python与MogoDB交互
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/15876175.html
Copyright © 2020-2023  润新知