編碼與模式------《Designing Data-Intensive Applications》讀書筆記5

weixin_34208283發表於2018-01-13

進入到第四章了,本篇主要聊的點是編碼(也就是序列化)與程式碼升級的一些場景,來梳理儲存之中涉及到的編解碼的流程。目前主流的編解碼便是來自Apache的Avro,來自Facebook的Thrift與Google的Protocolbuf,在本篇之中,我們也會一一梳理各種編碼的優點與痛點。

1.非二進位制的編碼格式

程式通常以至少兩種不同的表示方式處理資料:

1、在記憶體中,資料是儲存在物件、結構、列表、陣列、雜湊表、樹、等等。這些資料結構在記憶體之中被優化為CPU可以高效訪問和操作的結構(通常這是作業系統的任務,並不需要程式設計師操心)。

2、而當你想把資料寫入一個檔案或者通過網路傳送它時,你必須把它編碼成某種形式的位元組序列(例如,一個JSON文件)。

因此,我們需要兩種形式之間的某種轉換。(記憶體與其他位置)翻譯從記憶體中表示的資料稱之為編碼(也稱為序列化),反之稱為解碼(反序列化)。

通常編碼有如下幾種格式:

  • 特定的語言格式
    許多程式語言都對編碼有內建的支援,用於將記憶體物件編碼成位元組序列。例如:Java的java.io.Serializable , Ruby的Marshal, Python的pickle。但是這些程式語言內建的庫存在一些深層次的問題。

    • 編碼通常與特定的程式語言捆綁在一起,用另一種語言讀取資料是非常困難的
    • 為了在同一物件型別中恢復資料,解碼過程需要能夠例項化任意類,如果攻擊者可以讓您的應用程式解碼任意位元組序列,則它們可以例項化任意類。這常常是安全問題的來源。
    • 效率(用於編碼或解碼的CPU時間,以及編碼結構的大小),java內建編碼庫臭名昭著的就是其糟糕的表現和臃腫的編碼
  • JSON、XML與CSV
    上面這幾種格式,也是我們在編碼之中常見到的。

    • XML的描述十分精準,但是因過於冗長。
    • JSON的流行主要歸功於它在Web瀏覽器中的內建支援(由於它是JavaScript的一個子集)和相對於XML的簡單性。
    • CSV是另一種流行的與語言無關的格式,儘管功能不強。

    JSON、XML和CSV都是文字格式,因此都具有一定的可讀性。但他們也有如下一些微妙的問題:

    • 關於數字的編碼有很多歧義。在XML和CSV中,不能區分恰好由數字組成的數字和字串(除了引用外部模式)。JSON區分字串和數字,但它不區分整數和浮點數,也不能確認精度。
    • JSON與XML為Unicode字串的支援,但他們不支援二進位制字串(位元組序列沒有字元編碼)。
    • 對於XML和JSON,都有可選的模式支援。這些模式語言非常強大,因此學習和實現起來相當複雜。而CSV沒有任何模式,因此需要應用程式定義每個行和列的含義。如果應用程式新增了新行或列,則必須手動處理該更新。CSV是一個相當模糊的格式(出於是分隔符的原因)

2.二進位制的編碼格式

二進位制的編碼格式通常是最緊湊的編碼格式,對於一個小的資料集,編碼大小的收益是微不足道的,但一旦進入百萬兆位元組的資料集,資料格式的選擇就會有很大的影響了。接下來我們來看一個通過JSON描述的資料結構:


8552201-d62deda3a61020a5.png
使用JSON描述的資料結構
  • MessagPack
    我們來看看通過MessagePack進行二進位制編碼之後的JSON格式:
    8552201-3b5062e9ae394afc.png
    通過MessagePack進行編碼後的二進位制格式

    二進位制編碼長度為66個位元組,這僅比81位元組的文字JSON編碼小了一點。通過這樣的空間減少便喪失了可讀性的保障,我們來看看有木有更優秀的解決方式。
  • Thrift
    在Thrift中的資料進行編碼,需要預先在Thrift介面定義語言(IDL)中描述這樣的模式:
    8552201-1aaa2401441adf74.png
    通過IDL描述Thrift的資料格式

    在Thrift之中存在兩種不同的二進位制編碼格式,一種是直接使用二進位制編碼的Binary格式,另一種則是使用壓縮之後的Compact格式,我們來一一看兩者的區別。
8552201-2906f00215b576db.png
Binary格式

Binary格式編碼之後為59個位元組大小,並且每個欄位都有一個型別註釋(用於指示它是字串、整數、列表等),並在需要時指定長度指示(字串的長度、列表中項的數量)。但是和MessagePack相比就省去了欄位名等資訊,取而代之的是欄位標記(1,2和3),這些是出現在模式定義中的數字。欄位標記類似於欄位別名,它們是一種簡潔的方式來描述我們所談論的欄位,而不必拼寫欄位名稱。從而減少了二進位制編碼的大小。

8552201-9e070eb72957c497.png
Compact格式

Compact格式它包含相同的資訊只有34個位元組。它通過將欄位型別和標記號打包成一個位元組,並使用可變長度整數來實現這一點。它不是為1337號使用八個完整的位元組,而是用兩個位元組編碼,每個位元組的最高位用來指示是否還有更多的位元組要來。這意味著64到63之間的數字用一個位元組編碼,8192到8191之間的數字用兩個位元組編碼,較大的數字使用更多位元組。

  • ProtocolBuf
    Protocolbuf(只有一個二進位制編碼格式)相同的資料編碼如下圖所示。它位包裝略有不同,但Thrift的Compact格式大同小異。Protobuf以33位元組匹配相同的記錄。

    8552201-b30898fb38088d37.png
    ProtocolBuf的編碼格式

  • Avro
    Avro是一個二進位制編碼格式,它是發源於開源專案Hadoop,來作為Thrift的替換方案存在的,我們來看看通過Avro編碼之後的記錄,又是怎麼樣的呢?

    8552201-5cd981e6301893c3.png
    Avro的編碼格式

    在Avro模式之中沒有標記號。將同樣的資料進行編碼,Avro二進位制編碼是32個位元組長,是上述編碼之中最緊湊的。檢查上述的位元組序列,並沒有標識欄位或資料型別。編碼簡單地由連線在一起的值組成。在解析二進位制資料時,通過使用模式來確定每個欄位的資料型別。這意味著如果讀取資料的程式碼與寫入資料的程式碼使用完全相同的模式,二進位制資料才能被正確地解碼。

3.模式升級與演化

隨著應用程式的開發,模式不可避免地需要隨著時間而改變。而在這個過程之中,二進位制編碼同時保持向後和向前相容性呢?

  • 欄位標記

    • 從示例中可以看到,編碼的記錄只是編碼欄位的串聯。每個欄位由標籤號碼和註釋的資料型別識別(如字串或整數)。如果沒有設定欄位值,則只需從已編碼的記錄中省略該欄位值。因此欄位標記對編碼資料的含義至關重要。我們可以更改模式中欄位的名稱,因為編碼的資料從不引用欄位名稱,但不能更改欄位的標記,因為這將使所有現有編碼資料無效。
    • 可以通過新增一個新的標記號的方式向模式新增新欄位。如果舊程式碼(不知道您新增的新標記號)試圖讀取由新程式碼編寫的資料,包括一個新欄位,該欄位的標記號不識別,它可以簡單地忽略該欄位。資料型別註釋允許分析器來確定需要跳過多少位元組。因為每個欄位都有唯一的標記號,新程式碼可以無縫連線舊的資料,因為標記號仍然具有相同的含義。但是,如果是新增了一個新欄位,則不能使它成為必需欄位。如果要新增一個欄位並使其成為必需的欄位,那麼如果新程式碼讀取舊程式碼編寫的資料,則該檢查將失敗,因為舊程式碼將不會寫入您新增的新欄位。因此,為了保持向後相容性,在初始部署模式之後新增的每個欄位必須是可選的或具有預設值。
    • 刪除欄位就像新增欄位一樣,這意味著只能刪除一個可選的欄位(必填欄位不能被刪除),而且您不能再次使用相同的標記號(因為您可能還有一個包含舊標記號的資料,該欄位必須被新程式碼忽略)。
  • 資料型別
    如何改變欄位的資料型別?例如,將32位整數轉換為64位整數。新程式碼可以很容易地讀取舊程式碼編寫的資料,因為解析器可以用零填充任何丟失的位。但是,如果舊程式碼讀取由新程式碼編寫的資料,舊程式碼仍然使用32位變數來儲存值。如果解碼的64位值不適合32位,會被截斷。
    Protocolbuf並沒有一個列表或陣列的資料型別,而是有一個重複的標記欄位。可以將可選的(單值)欄位轉換為重複的(多值)欄位。讀取舊資料的新程式碼看到一個具有零個或一個元素的列表(取決於欄位是否存在);讀取新資料的舊程式碼只看到列表的最後一個元素。而Thrift有一個專門的列表資料型別,這是引數列表中的資料型別。這不允許像Protocolbuf那樣從單值到多值的升級,但它具有支援巢狀列表的優點。

  • 動態生成模式
    Avro最大的特點是支援了動態生成模式,它的核心思想是編碼者與解碼者的模式可以不同,事實上他們只需要相容就可以了。相比於Protocolbuf和Thrift,它並不包含任何標籤數字。每當資料庫模式發生變化時,管理員必須手動更新從資料庫列名到欄位標記的對映。而Avro是每次執行時簡單地進行模式轉換。任何讀取新資料檔案的程式都會感知到記錄的欄位發生了變化。

4.小結

編碼的細節不僅影響到工作效率,更重要的是會影響到應用程式和軟體的架構。Prorotocol Buf,Thrift 與 Avro,都使用一個模式來描述一個二進位制編碼格式。它們的模式語言比XML模式或JSON模式要簡單得多,它支援更詳細的驗證規則,並且能夠更好的進行模式的演化升級,在效能上也有了更好的提升。

相關文章