dubbo服務丟失問題

環境:dubbo-2.6.5(公司內部版本),註冊中心:zookeeper

問題描述

生產服務有兩個提供者,突然發現目前僅剩下了一個節點,登錄丟失的節點,查看服務是正常的,而且沒有任何error日誌。

問題分析

推斷1:dubbo admin頁面展示問題,服務實際是正常提供的

查看服務的應用日誌,發現服務沒有任何實際的調用,也就是說,消費者已經真的看不到該提供者,故不成立。

查看日誌

查看日誌定位到服務丟失的時間點。發現當天出現zk出現了大量的超時,原因是當天的zk主節點宕機了。

推斷2:dubbo zk上提供者節點數據丟失

查看zk下的提供者節點確實僅剩下了一個提供者節點數據。問題已定位那麼原因是什麼呢?

問題原因

問題是否出現在了dubbo對zk重連恢復數據這塊,開始查源碼。註冊中心源碼ZookeeperRegistry。

  1. 連接註冊zk:通過zkclient添加zk狀態監聽。並且繼承了FailbackRegistry各種失敗重試。
public class ZookeeperRegistry extends FailbackRegistry {
    public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
        ...
        // 1. 連接zk
        zkClient = zookeeperTransporter.connect(url);
        // 2. 添加zk狀態監聽
        zkClient.addStateListener(new StateListener() {
            @Override
            public void stateChanged(int state) {
                // 3. 重新連接後恢復動作,將當前的註冊服務於訂閱任務添加至重試列表中等待重試
                if (state == RECONNECTED) {
                    try {
                        recover();
                    } catch (Exception e) {
                        logger.error(e.getMessage(), e);
                    }
                }
            }
        });
    }
}

  1. zk客戶端:默認使用CuratorZookeeperClient實現
public class CuratorZookeeperClient extends AbstractZookeeperClient<CuratorWatcher> {
    public CuratorZookeeperClient(URL url) {
        ...
            client = builder.build();
            // dubbo對接zk連接狀態監聽器
            client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
                @Override
                public void stateChanged(CuratorFramework client, ConnectionState state) {
                    if (state == ConnectionState.LOST) {
                        CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED);
                    } else if (state == ConnectionState.CONNECTED) {
                        CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED);
                    } else if (state == ConnectionState.RECONNECTED) {
                        CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED);
                    }
                }
            });
            client.start();
        ...
    }
}
  1. 重試任務:註冊重新失敗重連任務FailbackRegistry中的DubboRegistryFailedRetryTimer,默認5秒檢查一次是否需要失敗恢復
public FailbackRegistry(URL url) {
    super(url);
    this.retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);
    this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            // Check and connect to the registry
            try {
				// failedRegistered失敗註冊重試,failedUnregistered失敗註銷重試,failedSubscribed失敗訂閱重試,failedUnsubscribed失敗取消訂閱重試,failedNotified失敗通知重試
                retry();
            } catch (Throwable t) { // Defensive fault tolerance
                logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
            }
        }
    }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
}

通過三部分的代碼我們可以推斷,如果zk狀態監聽與恢復部分出現問題可能會導致數據丟失問題。於是查看相關的api並且嘗試查看dubbo社區的問題與bug,果然發現了類似問題的修改與原因分析:https://github.com/apache/dubbo/pull/5135
問題的原因已經在代碼的註釋中說明,大體含義:如果ZNode數據已經存在,在會話超時期間,此時我們將重建一個數據節點,這個重複的異常原因可能是由於zk server中老的超時會話依然持有節點導致該節點的delete刪除事件延遲,並且zk server還沒有來得及去執行刪除,可能由這種場景引起。在這個情景下,我們可以本地刪除節點後再創建恢復節點數據。
個人理解是:如果會話斷開連接又重新連接成功。斷開連接發出的刪除節點事件,因爲延遲原因走在了重新連接恢復節點事件的後面。導致重新連接後沒能成功恢復節點。也就是我麼見到的,provider有一個節點服務正常,但是zk註冊中心中的提供者節點數據丟失,導致出現該節點對其他訂閱者不可見的現象

@Override
protected void createEphemeral(String path, String data) {
    byte[] dataBytes = data.getBytes(CHARSET);
    try {
        client.create().withMode(CreateMode.EPHEMERAL).forPath(path, dataBytes);
    } catch (NodeExistsException e) {
        logger.warn("ZNode " + path + " already exists, since we will only try to recreate a node on a session expiration" +
                ", this duplication might be caused by a delete delay from the zk server, which means the old expired session" +
                " may still holds this ZNode and the server just hasn't got time to do the deletion. In this case, " +
                "we can just try to delete and create again.", e);
        deletePath(path);
        createEphemeral(path, data);
    } catch (Exception e) {
        throw new IllegalStateException(e.getMessage(), e);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章