Encoding and Evolution 基礎 和 Json, XML, CSV, Binary variants (二進制變體)

Everything changes and nothing stands still.
—Heraclitus of Ephesus, as quoted by Plato in Cratylus (360 BCE)

上面那段話是書中的引言,數據系統無可避免的需要改變,我們在 Reliable, Scalable, and Maintainable Application 時有介紹過 Evolvability (進化能力) ,我們設計的系統應該讓改變變的簡單;當資料格式或 schema 有變更時,相對應系統的新版本程式應該要同時部署且執行,然而,在大型的數據系統中,程式變更是不會立即發生的:

  • 於 server-side 部署系統時,你會希望執行 rolling upgrade (滾動式升級),意思為一次只部署新版本的程式到小量的機器上,讓系統能持續運作,且能做到服務不斷線,因此就能鼓勵更密集的釋出新版本程式,讓系統更有 evolvability (進化能力)。
  • 在 client-side,你只能希望 user 能更新應用軟體,所以會有一群沒同步更新應用軟體的 user。

這意味著舊的程式跟新的程式要並行,代表系統要同時:

  • Backward compatibility (向前兼容) 新程式要能讀舊程式寫入的資料。
  • Forward compatibility (向後兼容) 舊程式要能讀新程式寫入的資料。

向前兼容很好理解,你在寫與新 schema 有關的程式時就會一併考慮舊的資料該怎麼讀取,向後兼容就有點 tricky 了,代表你的程式要能忽略所有未來可能加入的額外資訊或欄位。

在往後幾天會介紹數個資料編碼方式,包含 JSON, XML, Protocol Buffers, Thrift 和 Avro,然後也會看該如何兼容,最後則會提到資料溝通模式,像 REST, RPC, 訊息佇列。

Format for Encoding Data

資料在程式中通常會以 2 種方式表達 (最少 2 種) :

  • 在記憶體中,資料會以物件、陣列、hash table、樹等等的方式儲存,感謝許多大神,這些資料結構的操作存取是以 CPU 最佳化方模式設計。
  • 當你寫入資料到檔案或透過網路傳遞資料,你必須轉成一連串的位元組,不同的資料結構會讓結果看起來不一樣。

所以,我們必須有某種翻譯方法讓程式得以在這 2 種方式中做切換,從記憶體中的資料結構翻譯成連續的位元組稱為 encoding (編譯),也可稱 serialization (序列化) 或 marshalling (編組);反之稱做 decoding (解譯),也可稱 parsing, deserializationunmarshalling,接下來就來介紹各種不同的 encoding 格式啦!

Language-Specific Format

顧名思義,就是每個程式語言內建的 encoding 格式啦,比如 Java 就是 java.io.Serializable,Ruby 是 Marshal,Python 是 pickle ,除此之外還有其他第三方 library 提供,例如 Java 的 Kryo,這些 encoding 方式看起來方便,但可能會有以下幾個問題:

  • 程式語言被限制住了,例如你很難用 Java 去讀 Python encoding 後的 pickle 檔案。
  • 資料版本通常是後來的想法,當你想快速完成 encoding 工作時,就會忽略了未來向前和向後兼容的問題。
  • 執行效率也是,例如 Java 內建的 serialization 執行效率就很不好

所以正常工程師會選擇其他的 encoding 格式。

我們公司只有在做 POC 專案的時候才會考慮用 Language-Specific Format,在正式開發時會全部替換掉,畢竟你不能圖一時的方便然後讓未來的工程想來砍你,有時寫程式時會想像未來接手的工程師都是喪心病狂的瘋子 XD

JSON, XML, CSV 和 Binary Variants (二進制變體)

JSON, XML, CSV 都是很廣泛為人知、多人使用、支援工具很多的 encoding 格式,但不喜歡的人也不少,XML 常常被詬病它的複雜性,不必要的資訊太多了,相較之下 JSON 就比較好懂了,在許多的 web 應用系統中也是內建 JSON 在裡頭,而 CSV 雖然沒這麼強,但它是很多檔案交換時用的格式。

JSON, XML, CSV 都是人肉眼看的懂的文字格式 (XML 就…),儘管他們流行,但還是可能會有以下幾個微妙的問題:

  • 它們對數字的 encoding 很模糊,比如在 XML 和 CSV,你不曉得這個欄位究竟是數字還是文字 (除非你有另外對欄位設計額外的 schema);而 JSON 雖然區分的出來,但 JSON 無法區分是整數還是浮點數 (因為未指定精度),這個問題會造成當你數字大於 $2^{53}$ 時不能表示成 IEEE 754 雙精度浮點數,簡單說就是用 javascript 解析該數字會有不正確結果。
  • 它們不支援 binary strings 格式,JSON, XML 因為都是人看得懂的內容,所以對 Unicode 有良好的支援,可是若你欄位想存純粹的 binary 內容進去就不行了,你只能把 binary 轉成 Base64-encoded 字串後才能存,但這樣會增加差不多 33% 的大小。
  • JSON, XML 雖然也支援 schema,但大多數的主流工具都不會實做。
  • CSV 不支援 schema,所以應用程式需知道自己在讀什麼;另外 CSV 對逗號和換行符號這事很敏感,雖然 CSV 的規格上有規定遇到某些特殊字元該如何處理,但不是所有的解析器都會乖乖實做。

雖然如此,它們在很多資料交換情況下也是相當方便的。

Binary Encoding (二元編碼)

想像一下你需要在內部做 TB 等級的資料交換時,你會直接用 JSON 當文字檔案儲存後給別人用嗎?如果你是用雲端儲存資源,小小的改變也能幫你節省非常多的資料大小,Binary Encoding 就是幫助我們省節資源用的方法;JSON 跟 XML 都有許多 Binary Encoding 的工具,JSON 有 MessagePack, BSON, BJSON, UBJSON, BISON 等等,XML 也有 WBXML, Fast Infoset 等等,

因為 JSON 和 XML 不會特別指定 schema,代表我們在做 Binary Encoding 時勢必要存欄位名稱,以下這串 JSON 為之後所有 encoding 格式的範例:

JSON Example 4-1

{
    "userName": "Martin",
    "favoriteNumber": 1337,
    "interests": ["daydreaming", "hacking"]
}

就來看 MessagePack 怎麼做 Binary Encoding 的吧!

figure_4-1

首先要知道的是圖中的內容皆為 16 進位,再來就一步步解析吧!

  1. 第 1 個位元組 0x83 代表接下來的資料是個物件 (前 4 個位元 = 0x80),然後裡頭有 3 個欄位 (後 4 個位元= 0x03)。(若超過 15 個欄位會有不同的表達方式,就自己查文件吧XD)
  2. 在來第 2 個位元組 0xa8 代表接下來會有個字串 (前 4 個位元 0xa0 ),然後長度為 8 個位元組 (後 4 個位元 0x08)。
  3. 所以在來就是 8 個位元組 存 userName (使用 ASCII),這裡不需要一個表達字串結束的字元。
  4. 再來 0xa6 也是跟第 2 步一樣意思,有個字串長度為 6 位元組 ,後面就是造著這邏輯把檔案寫完了。

經過 MessagePack 的 binary encoding 後的大小為 66 位元組,比純粹把 JSON 當文字儲存後的 81 位元組 來的小 (移除空白),所有的 JSON binary encoding 做法都差不多,但明天要講的 encoding 格式檔案大小就只剩 32 位元組 了。

tshine73
tshine73
文章: 50

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *