【分散式】Zookeeper原始碼分析:Jute序列化
概要
Zookeeper的客戶端和服務端進行網路通訊實現資料傳輸使用了序列化元件Jute
,它最初是Hadoop中預設的序列化元件(Record IO)中的序列化元件,後來Hadoop從0.21.0版本開始廢棄了Record IO,而使用Avro這個序列化框架,而Zookeeper官方由於一些歷史原因依然使用了Jute這個古老的序列化元件,它對資料的序列化和反序列化操作是zookeeper高效傳輸資料的基礎。本文對Jute在網路通訊底層的一些關鍵技術點進行分析,主要內容如下:
目錄
第1部分 Jute介紹
Jute部分主要在org.apache.jute
Jute主要用於Zookeeper進行網路傳輸和本地磁碟資料的序列化及反序列化工作。首先附上整體框架:
Record介面
Jute中定義了自己獨特的序列化格式Record
,Zookeeper中所有需要進行網路傳輸或者本地磁碟儲存的型別定義都實現了該介面。該介面中有兩個方法:serialize
和deserialize
,所有繼承它的類通過這兩個方法定義序列化和反序列化的方式,其中OutputArchive
和InputArchive
是真正的序列化器和反序列化器。
public interface Record { public void serialize(OutputArchive archive, String tag) throws IOException; public void deserialize(InputArchive archive, String tag) throws IOException; }
注意,這兩個方法中都有引數tag
,這是因為每個Archive
可以包含對多個物件的序列化和反序列化,這兩個介面可以用於標識物件。以RequestHeader
為例:
public void serialize(OutputArchive a_, String tag) throws java.io.IOException { a_.startRecord(this,tag); a_.writeInt(xid,"xid"); a_.writeInt(type,"type"); a_.endRecord(this,tag); } public void deserialize(InputArchive a_, String tag) throws java.io.IOException { a_.startRecord(tag); xid=a_.readInt("xid"); type=a_.readInt("type"); a_.endRecord(tag); }
RequestHeader
中包含了xid
和type
兩個屬性,序列化/反序列化遵循三個步驟:
- startRecord
- read/writeXXX
- endRecord
OutputArchive和InputArchive介面
序列化和反序列化介面的定義部分,在Zookeeper中分別有BinaryOutputArchive/BinaryInputArchive
、CsvOutputArchive\CsvInputArchive
、XmlOutputArchive\XmlInputArchive
三種實現,基於Binary是Zookeeper中最主要的序列化方式。
Index介面
用於vector
或map
中指示器,如:
Index idx = startVector(...);// 開始讀取vector
while (!idx.done()) { // 判斷是否讀/寫完
// read element of a vector
idx.incr(); //index自增,即讀/寫下一個
}
第2部分 通訊協議
Zookeeper在TCP/IP
基礎上實現了自己的通訊協議,主要在 org.apache.zookeeper.proto
包中。
- 請求:包含請求頭(RequestHeader)和請求體(XXXRequest)
- 響應:包含響應頭(Response)和響應體(XXXResponse)
請求協議
以下是一個獲取節點資料請求的完整協議定義:
協議內容解析:
RequestHeader請求頭
包含xid和type,xid使用者記錄客戶端發起的先後序號,確保單個客戶端請求的響應順序;type代表請求的操作型別,包括OpCode.create
、OpCode.delete
、OpCode.getData
、OpCode.getChildren
等。根據協議規定,除了會話建立
的請求,所有的請求必須包含請求頭。
XXXRequest請求體
請求體根據請求型別的不同,Zookeeper中有不同的請求體實現。
- ConnectRequest
![image-20190108195626130](/Users/didi/Library/Application Support/typora-user-images/image-20190108195626130.png)
Zookeeper在建立會話時,請求體中包含了協議的版本號、最近一次接收到的伺服器的Zxid,會話超時時間、會話id和會話密碼,如果建立的會話包含會話id和密碼則認為客戶端正在進行會話重連。
- SetDataRequest
客戶端在傳送更新節點資料時會發送該請求,請求體中包含資料節點路徑、資料內容data和節點資料期望的版本號。
![image-20190108200416068](/Users/didi/Library/Application Support/typora-user-images/image-20190108200416068.png)
- CreateRequest
客戶端新建節點時會發送該請求,請求體中包含節點路徑,資料內容,acl認證內容和節點型別。
![image-20190108200749670](/Users/didi/Library/Application Support/typora-user-images/image-20190108200749670.png)
在Zookeeper中,節點型別包含四種:
PERSISTENT (0, false, false), // 永久節點
PERSISTENT_SEQUENTIAL (2, false, true), //永久順序節點
EPHEMERAL (1, true, false), //臨時節點
EPHEMERAL_SEQUENTIAL (3, true, true); //臨時順序節點
響應協議
以下是一個獲取資料節點的響應的完整協議:
ReplyHeader響應頭
包含每一個響應最基本的資訊,包括客戶端發起的先後序號xid,Zookeeper伺服器上最新的事務ID zxid,可以根據返回錯誤碼err判斷請求響應情況,如處理成功(Code.OK: 0)、節點不存在(Code.NONODE: 101)、沒有許可權(Code.NOAUTH: 102)。
XXXReponse響應體
響應體是響應的主體部分,不同的響應型別具有不同的響應體。
- ConnectResponse
針對客戶端的會話建立請求,服務端會返回客戶端一個ConnectResponse響應,該響應體包含了版本號protocolVersion、會話的超時時間timeOut、會話標識sessionId和會話密碼passwd。
- SetDataResponse
針對客戶端的更新節點資料請求,服務端會返回客戶端一個SetDataResponse響應,該響應體包含了最新的節點狀態stat。
- CreateResponse
針對客戶端的建立節點請求,服務端會反回客戶端一個CreateResponse響應,該響應體包含了建立節點的路徑。
第3部分 總結
本文介紹了Jute序列化元件及客戶端與服務端、服務端與服務端的通訊協議,比較簡單,但是它是Zookeeper進行分散式通訊的基礎,值得一看。