Encoding and Evolution (3) – Apache Avro

Avro

最後一個要來談的 binary encoding 方式是 Apache Avro ,閞始於 Hadoop 底下的子專案,它很明顯的跟 Thrift 和 Protocol Buffers 不同,Avro 一樣需要 schema 來定義欄位,Avro 能用 2 個 schema 語言,一個 (Avro IDL – Interface description language) 比較適合人讀,另一個 JSON 版本比較適合機器讀,我們一樣拿這份 JSON 資料來看看 Avro 的 2 種 schema 長什麼樣子,

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

首先是 Avro IDL:

record Person {
    string userName;
    union { null, long } favoriteNumber = null; 
    array<string> interests;
}

再來是 JSON 版 schema:

{
        "type": "record",
        "name": "Person",
        "fields": [
                    {"name": "userName", "type": "string"},
                    {"name": "favoriteNumber", "type": ["null", "long"], "default": null},                          {"name": "interests", "type": {"type": "array", "items": "string"}}
                ] 
}

這裡可以看到,Avro 沒有在 schema 中使用 欄位標籤 (field tags),它 encoding 後的檔案大小是最強大的 32 位元組,encoding 結果細節如下圖:

figure_4-5

Avro 沒有欄位標籤 (field tags) ,也沒有欄位型態,上面只看到說 長度 為何,然後後面就直接接資料了,所以同樣長度下你可以是數字或其他東西,然後 Avro 的數字也是用可變動長度儲存,這點跟 Thrift 和 Protocol Buffers 一樣,

既然沒有欄位標籤跟型態,所以我們如何跟 schema 做對應呢?我們只能用欄位 排序 告知 Avro 每個欄位的資料型態是什麼,也就是說在 decoding 資料時,必須使用 與寫資料時相同的 schema ,任何不匹配的欄位都會造成 decoding 時不正確。

Writer’s schema and Reader’s schema

這裡 Avro 把 encoding 跟 decoding 動作分成 Writer (寫入者) 和 Reader (讀取者),也就是寫入時會有 寫入者的 schema,讀取時也會有讀取者的 schema,因為應用軟體會進化、改變的關係, 2 邊的 schema 有時會不同,所以 Avro 是怎麼做到並存呢?

當 Avro 讀取資料時,它會同時看寫入者跟讀取者的 schema,然後從寫入者的 schema 翻譯資料到讀取者的 schema 裡,如下圖說明:

figure_4-6

這裡可以看到,不會因為 2 邊欄位的順序不同造成問題,Avro 在解析時會一個欄位名接著一個欄位名解析,若寫入者 schema 有該欄而讀取者 schema 沒有,則 Avro 會忽略,若讀取者有新欄位而寫入者沒有,則會以讀取者 schema 所設定的方式給定預設值,整成成表格後如下:

寫入者的 schema讀取者的 schemaApp Action
YN忽略
NY用讀取者 schema 定義的來給預設值

schema 進化的規則

向後兼容代表你寫入者的 schema 是新的,而讀取者的 schema 是舊的,反過來說,

向前兼容代表你寫入者的 schema 是舊的,而讀取者的 schema 是新的,

所以為了保持一致性,你可能只能 新增刪除 帶有 預設值 的欄位,舉例來說,如果你 新增 一欄位是沒有預設值的,新讀取者將無法讀取舊寫入者的資料,這就破壞了向前兼容,再繼續說若你 刪除 了一個沒有預設值的欄位,則舊的讀取者將無法讀取新寫入者寫入的資料,這就破壞了向後兼容。

在某些程式語言,null 是可被允許的預設值,但它不是一個 Avro 用的欄位型態,你可以用另一種 Avro 支援的方式: union type 來讓預設值設為 null;舉例來說,有一欄位型態為 union { null, long, string} ,它說明了該欄位可以是 null、long 或 string,此時你就可以把預設值設定為 null,只要它是在 union 中的某個分支型態之一就 OK,

因此,Avro 沒有像 Thrift 和 Protocol Buffers 那樣的 requiredoptional 的標註,取而代之就是用 union 加預設值來實現。

其他的改變可能嗎?

改變欄位資料型態是沒問題的,Avro 會幫我們轉換型態,

改變欄位名稱也是沒問題但有點 tricky,讀取者的 schema 可以為改變後欄位名取 alias name (別名),之後它就可以對舊寫入者資料用 alias name 來解析,所以向後兼容沒問題,但向前兼容就 GG 了 (舊讀取者遇到不認識的 schema 會忽略),同理新增 union 裡頭的型態也是一樣的。

tshine73
tshine73
文章: 51

發佈留言

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