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 就能搞定了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM