ZooKeeper ( 簡稱 zk ) 是一個開源的分佈式協調服務,其經常使用作分佈式服務的註冊中心html
本文要講的是 Node.js 的 zk sdk -- node-zookeeper-clientnode
在這以前,先了解一下 zk 相關的基本知識git
ZooKeeper Transaction Id
,用於保證 zk 分佈式一致性[總長度, header, payload]
先從使用示例開始github
/** * xiedacon created at 2019-06-14 10:07:46 * * Copyright (c) 2019 Souche.com, all rights reserved. */ 'use strict'; const ZK = require('node-zookeeper-client'); const util = require('util'); const client = ZK.createClient('127.0.0.1:2181'); client.getChildren = util.promisify(client.getChildren); (async () => { console.log('Result:', await client.getChildren('/xxx')); })().catch((err) => { console.log(err); }); client.connect();
上面的代碼總共分紅 3 步:apache
ZK.createClient('127.0.0.1:2181')
,建立 zk 客戶端實例client.connect()
,與 zk 服務端創建鏈接client.getChildren('/xxx')
,獲取 /xxx 節點下的全部子節點zk 客戶端可大體分爲三塊:服務器
index.js
lib/connectionManager.js
lib/watcherManager.js
client.connect()
,connect 方法內調用 connectionManager.connect()
,將 connectionManager 狀態置爲 CONNECTING findNextServer()
獲取一個 zk 服務器地址connect
、data
、drain
、close
、error
事件若是 tcp 鏈接創建成功,則觸發 connect 事件,並執行 onSocketConnected
方法session
若是 tcp 鏈接創建失敗,則分別觸發 error
、close
事件,並執行 onSocketError
、onSocketClosed
方法app
若是 zk 服務器容許 client 接入socket
onSocketData
方法,此時 connectionManager 狀態爲 CONNECTING connectResponse.timeOut <= 0
意味着當前 client 的 session 過時:將 connectionManager 狀態置爲 SESSION_EXPIRED,稍後 zk 服務器將自動關閉 socket,即觸發 5drain
,發送阻塞 request,使數據流動起來若是 zk 服務器拒絕 client 接入async
PS:其實並無觸發 socket drain
的代碼,而是調用的 onPacketQueueReadable
方法,但它們的效果同樣
client.getChildren('/xxx')
connectionManager.queue
,將 request 放入 packetQueue 中,同時觸發 socket drain
onPacketQueueReadable
方法中,對於非 Auth、Ping 等的外部 request,設置 xiddata
事件,此時 connectionManager 狀態爲 CONECTED 若是 xid 爲內部 xid 則進行內部處理,不然對比 pendingQueue 隊頭 request 的 xid 與當前 xid 是否一致
client.getChildren
的回調方法PS:其實並無觸發 socket drain
的代碼,而是觸發 packetQueue 的 readable
事件,但它們的效果同樣
各方法與狀態機的關係以下:
node-zookeeper-client 自己提供斷線重連的能力
對於長期斷線 ( 30s 以上 ),鏈接從新創建後,當前客戶端的 session 已通過期,connectionManager 將進入 SESSION_EXPIRED 狀態,此時調用 queue
方法會拋出 SESSION_EXPIRED
錯誤
這一問題在 zookeeper 文檔 中也有提到:
It means that the client was partitioned off from the ZooKeeper service for more the the session timeout and ZooKeeper decided that the client died.
當客戶端收到 SESSION_EXPIRED
錯誤時,那意味着當前客戶端已經從 ZooKeeper 服務中斷開,即 sesssion 過時,ZooKeeper 斷定當前客戶端應當關閉。
If the client is only reading state from ZooKeeper, recovery means just reconnecting. In more complex applications, recovery means recreating ephemeral nodes, vying for leadership roles, and reconstructing published state.
若是客戶端以只讀方式鏈接的 ZooKeeper,那麼只須要從新鏈接就行了。但在更復雜的狀況下,重連就意味着從新建立臨時節點,參與 leader 競爭,重建已發佈的狀態。
所以 node-zookeeper-client 的作法也合情合理,當 session 過時時直接禁止發送,由調用程序決定是否重連,重連後須要作什麼操做
但在大多數狀況下,node-zookeeper-client 只會做爲一個簡單的客戶端鏈接 zookeeper 服務,不會參與 zk 選舉,所以,它最多須要作的事情也只是恢復臨時節點
目前的方案是 session 過時自動重連,重連成功後將會進入 CONNECTED 狀態並觸發 connect
事件,在事件內從新註冊臨時節點
在 zk 集羣中,不一樣服務節點之間經過 ZAB 協議,保證分佈式一致性。zxid ( ZooKeeper Transaction Id ) 就是用於保證分佈式一致性的標識符
zk 直接掛掉或者手動重啓都將會致使集羣 zxid 重置。當客戶端重連時,因爲客戶端自己存儲的 xzid 大於 zk 集羣 xzid,此時,zk 集羣將拒絕客戶端鏈接。外部表現爲 connectionManager 瘋狂執行 connect 操做,全部調用阻塞,zk 日誌輸出 Refusing session request for client /192.168.0.1:33400 as it has seen zxid 0x300000012 our last zxid is 0x0 client must try another server
雖然 zk 集羣判斷出 zxid 有誤,但直接斷開鏈接的處理方式確實是有待考量。由於經過這種方式處理,客戶端徹底不知道發生了什麼,只能經過重連來解決,然而重連又鏈接不上,這就造成了一個死循環
因爲客戶端無需在乎 zk 集羣版本,所以,能夠在每次鏈接時,直接重置 zxid 便可