本文為 Design Data Intensive Applications 的書摘 + 個人心得。
再來要講的 binary encoding 工具就是 Apache Thrift 和 Protocol Buffers (protobuf),Protocol Buffers 是 Google 開發的,Thrift 是 Facebook,它們的概念差不多,都是需要為資料額外定義 schema,一樣繼續使用這份 JSON 資料來說明:
JSON Example 4-1
{
"userName": "Martin",
"favoriteNumber": 1337,
"interests": ["daydreaming", "hacking"]
}
然後 Thrift 的 schema 長這樣:
struct Person {
1: required string userName,
2: optional i64 favoriteNumber,
3: optional list<string> interests
}
Protocol Buffers 的話類似長這樣:
message Person {
required string user_name = 1;
optional int64 favorite_number = 2;
repeated string interests = 3;
}
Thrift 跟 Protocol Buffers 都有程式碼產生工具 (code generation) 來產生不同程式語言的程式,你的系統只要呼叫產生好的程式做 encode 和 decode,Thrift 有 2 種不同的 binary encoding 格式,為 BinaryProtocol 和 CompactProtocol,首先就來看 BinaryProtocol 的結果吧,大小為 59 bytes,
![figure_4-2](https://it-iron.s3-ap-northeast-1.amazonaws.com/2020/figure_4-2.png)
看起來跟昨天講到的 MessagePack encoding 結果很像,第一個位元組是欄位型態,再來 2 個位元組是 欄位標籤 (field tag),再來的 4 個位元組說字串長度為 6,然後接下來 6 個位元組就是用 ASCII 編碼後的位元組 Martin ,以此類推,
這裡可以看到最大不同的是 Thrift 沒有存欄位名稱 (userName, favoriteNumber, interests),光這一點就省下不少空間了,取而代之的是用 欄位標籤 (field tag) 來當作欄位別名,
然後 Thrift 的下一個格式 CompactProtocol encoding 結果跟 BinaryProtocol 類似,如下圖 4-3,大小為 34 位元組,最大的不同是 CompactProtocol 把欄位型態跟欄位標籤併成一個位元組,另一個就是使用可變動大小的位元組來表示數字,例如一個位元組能表示 -64 ~ 63 的數字,2 個位元組能表示 -8192 ~ 8191 的數字,所以 1337 就只被寫成 2 個位元組,而不是 i64 這個整數型態的完整 8 個位元組。
![figure_4-3](https://it-iron.s3-ap-northeast-1.amazonaws.com/2020/figure_4-3.png)
最後來看 Protocol Buffers,它只有一種 binary encoding 格式,encoding 的結果大小為 33 位元組:
![figure_4-4](https://it-iron.s3-ap-northeast-1.amazonaws.com/2020/figure_4-4.png)
Thrift 跟 Protocol Buffers schema 的每個欄位都能設 required 和 optional ,皆是在執行時才會做檢查,以利幫助工程師找 bug。
最後要來看,若 schema 會隨著時間改變,Thrift 和 Protocol Buffers 如何保持 Encoding and Evolution 基礎 講過的向前和向後兼容能力呢?
schema 演進 – 欄位標籤 (field tags)
欄位標籤 (field tags) 對 Thrift 和 Protocol Buffers 是不能隨意修改,但欄位名稱沒這麼重要,可以隨時改,
初始版本的 schema 確定後,新增欄位時務必要把新欄位設定為 **optional **或給他 初始值,因為:
- Backward compatibility (向前兼容) – 新程式要能讀舊程式寫入的資料 非 required 的情況下新程式舊資料時會忽略新欄位。
- Forward compatibility (向後兼容) – 舊程式要能讀新程式寫入的資料 若把新欄位設 required ,舊程式讀到新程式寫入的資料時就爆炸了。
刪除欄位也是一樣道理, 欄位標籤不變的情況下你只能移除 optional 的欄位,然後你 不能再使用一樣的欄位標籤。
schema 演進 – 欄位型態 (data types)
你可以把 32-bit 的整數改成 64-bit 整數,因為新程式再讀舊資料時會補足不足的位元,但舊程式在讀新資料時會爆掉,其他的欄位型態改變可參考官方文件。
另一個可以改變型態的是 Protocol Buffers 的 repeated 型態 repeated string interests = 3;
,它能把單一值變多值,Protocol Buffers 沒有 list 或 array 的資料型態,取而代之的是 repeated ,表示這欄位會出現多次,所以你可以把 optional 改為 repeated,舊程式讀新資料時只會用最後一筆的資料,新程式讀舊資料時會視為長度為 0 或 1 的 list。
雖然 Thrift 沒有這個好處,但它的 list 型態就能支援巢狀結構。