基于TCP/IP協議,ZooKeeper實現了自己的通信協議來完成客戶端與服務端、服務端與服務端之間的網絡通信。ZooKeeper通信協議整體上的設計非常簡單,對于請求,主要包含請求頭和請求體,而對于響應,則主要包含響應頭和響應體,如下圖所示。
我們首先來看請求協議的詳細設計,下圖定義了一個“獲取節點數據”請求的完整協議定義。
接下來,我們將從請求頭和請求體兩方面分別解析ZooKeeper請求的協議設計。
請求頭中包含了請求最基本的信息,包括xid和type:
xid用于記錄客戶端請求發起的先后順序,用來確保單個客戶端請求的響應順序。type代表請求的操作類型,常見的包括創建節點(OpCode.create:1)、刪除節點(OpCode.create:2)和獲取節點數據(OpCode.getData:4)等,所有這些操作類型都被定義在類org.apache.zookeeper.ZooDefs.OpCode中。根據協議規定,除非是“會話創建”請求,其他所有的客戶端請求中都會帶上請求頭。
協議的請求體部分是指請求的主體內容部分,包含了請求的所有操作內容。不同的請求類型,其請求體部分的結構是不同的,下面我們以會話創建、獲取節點數據和更新節點數據這三個典型的請求體為例來對請求體進行詳細分析。
ZooKeeper客戶端和服務器在創建會話的時候,會發送ConnectRequest請求,該請求體中包含了數據節點的節點路徑path和是否注冊Watcher的標識watch,其數據結構定義如下:
ZooKeeper客戶端在向服務器發送更新節點數據請求的時候,會發送SetDataRequest請求,該請求體中包含了數據節點的節點路徑path、數據內容data和節點數據的期望版本號version,其數據結構定義如下:
以上介紹了常見的三種典型請求體定義,針對不同的請求類型,ZooKeeper都會定義不同的請求體。
上面我們分別介紹了請求頭和請求體的協議定義,現在我們通過一個客戶端“獲取節點數據”的具體例子來進一步了解請求協議。
/**
* 發起一次簡單的獲取節點數據請求
*
*/
public class A_simple_get_data_request implements Watcher {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 5000, new A_simple_get_data_request());
zk.getData("/&7_2_4/get_data", true, null);
}
public void process(WatchedEvent arg0) {
}
}
上面是一個發起一次簡單的獲取節點數據內容請求的樣例程序?蛻舳苏{用getData接口,實際上就是向ZooKeeper服務端發送了一個GetDataRequest請求。使用Wireshark獲取到其發送的網絡TCP包,如下圖所示。
在上圖中,我們獲取到了ZooKeeper客戶端請求發出后,在TCP層數據傳輸的十六進制表示,其中帶下劃線的部分就是對應的GetDataRequest請求,即“[00,00,00,1d,00,00,00,01,00,00,00,04,00,00,00,10,2f,24,37,5f,32,5f,34,2f,67,65,74,5f,64,61,74,61,01]”。
十六進制位 | 協議部分 | 數值或字符串 |
---|---|---|
00,00,00,1d | 0~3位是len,代表整個請求的數據包長度 | 29 |
00,00,00,01 | 4~7位是xid,代表客戶端請求的發起序號 | 1 |
00,00,00,04 | 8~11位是type,代表客戶端請求類型 | 4(代表OpCode.getData) |
00,00,00,10 | 12~15位是len,代表節點路徑的長度 | 16(代表節點路徑長度轉換成十六進制是16位) |
2f,24,37,5f, 32,5f,34,2f, 67,65,74,5f, 64,61,74,61 |
16~31位是path,代表節點路徑 | /&7_2_4/get_data(通過比對ASCII碼表轉換成十進制即可) |
01 | 32位是watch,代表是否注冊Watcher | 1(代表注冊Watcher) |
上面我們已經對ZooKeeper請求部分的協議進行了解析,接下來我們看看服務器端響應的協議解析。我們首先來看響應協議的詳細設計,下圖定義了一個“獲取節點數據”響應的完整協議定義。
響應頭中包含了每一個響應最基本的信息,包括xid、zxid和err:
xid和上文中提到的請求頭中的xid是一致的,響應中只是將請求中的xid原值返回。zxid代表ZooKeeper服務器上當前最新的事務ID。err則是一個錯誤碼,當請求處理過程中出現異常情況時,會在這個錯誤碼中標識出來,常見的包括處理成功(Code.OK:0)、節點不存在(Code.NONODE:101)和沒有權限(Code.NOAUTH:102)等,所有這些錯誤碼都被定義在類org.apahce.zookeeper.KeeperException.Code中。
協議的響應體部分是指響應的主體內容部分,包含了響應的所有返回數據。不同的響應類型,其響應體部分的結構是不同的,下面我們以會話創建、獲取節點數據和更新節點數據這三個典型的響應體為例來對響應體進行詳細分析。
針對客戶端的會話創建請求,服務端會返回客戶端一個ConnectResponse響應,該響應體中包含了協議的版本號protocolVersion、會話的超時時間timeOut、會話標識sessionId和會話密碼passwd,其數據結構定義如下:
針對客戶端的獲取節點數據請求,服務端會返回客戶端一個GetDataResponse響應,該響應體中包含了數據節點的數據內容data和節點狀態stat,其數據結構定義如下:
針對客戶端的更新節點數據請求,服務端會返回客戶端一個SetDataResponse響應,該響應體中包含了最新的節點狀態stat,其數據結構定義如下:
以上介紹了常見的三種典型響應體定義,針對不同的響應類型,ZooKeeper都會定義不同的響應體。
在上面的內容中,我們分別介紹了響應體和響應體的協議定義,現在我們再次通過上文中提到的客戶端“獲取節點數據”的例子來對響應協議做一個實際分析。
這里的測試用例使用上面的示例程序(即A_simple_get_data_request類),只是這次我們使用Wireshark獲取到服務端響應客戶端時的網絡TCP包,如下圖所示。
在上圖中,我們獲取到了ZooKeeper服務端響應發出之后,在TCP層數據傳輸的十六進制表示,其中帶下劃線的部分就是對應的GetDataResponse響應,即“[00,00,00,63,00,00,00,05,00,00,00,00,00,00,00,04,00,00,00,00,00,00,00,0b,69,27,6d,5f,63,6f,6e,74,65,6e,74,00,00,00,00,00,00,00,04,00,00,00,00,00,00,00,04,00,00,01,43,67,bd,0e,08,00,00,01,43,67,bd,0e,08,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,0b,00,00,00,00,00,00,00,00,00,00,00,04]”
十六進制位 | 協議部分 | 數值或字符串 |
---|---|---|
00,00,00,63 | 0~3位是len,代表整個響應的數據包長度 | 99 |
00,00,00,05 | 4~7位是xid,代表客戶端請求發起的序號 | 5(代表本次請求是客戶端會話創建后的第5次請求發送) |
00,00,00,00, 00,00,00,04 |
8~165位是zxid,代表當前服務端處理過的最新的ZXID值 | 4 |
00,00,00,00 | 16~19位是err,代表錯誤碼 | 0(代表Code.OK) |
00,00,00,0b | 20~23位是len,代表節點數據內容的長度 | 11(代表接下去11位是數據內容的字節數組) |
69,27,6d,5f, 63,6f,6e,74, 65,6e,74 |
24~34位是data,代表節點的數據內容 | i'm_content |
00,00,00,00, 00,00,00,04 |
35~42位是czxid,代表創建該數據節點時的ZXID | 4 |
00,00,00,00, 00,00,00,04 |
43~50位是mzxid,代表最后一次修改該數據節點時的ZXID | 4 |
00,00,01,43, 67,bd,0e,08 |
51~58位是ctime,代表數據節點的創建時間 | 13890148779752(即:2014-01-06 21:27:59) |
00,00,01,43, 67,bd,0e,08 |
59~66位是mtime,代表數據節點最后一次變更的時間 | 13890148779752(即:2014-01-06 21:27:59) |
00,00,00,00 | 67~70位是version,代表數據節點的內容的版本號 | 0 |
00,00,00,00 | 71~74位是cversion,代表數據節點的子節點版本號 | 0 |
00,00,00,00 | 75~78位是aversion,代表數據節點的ACL版本號 | 0 |
00,00,00,00, 00,00,00,00 |
79~86位是ephemeralOwner,如果該數據節點是臨時節點,那么就記錄創建該臨時節點的會話ID,如果是持久節點,則為0 | 0(代表該節點是持久節點) |
00,00,00,0b | 87~90位是numChildren,代表數據節點的數據內容長度 | 11 |
00,00,00,00 | 91~94位是numChildren,代表數據節點的子節點個數 | 0 |
00,00,00,00, 00,00,00,04 |
95~102位是pzxid,代表最后依次對子節點列表變更的PZXID | 4 |