avro格式詳解

語言: CN / TW / HK

【Avro介紹】

Apache Avro是hadoop中的一個子專案,也是一個數據序列化系統,其資料最終以二進位制格式,採用行式儲存的方式進行儲存。

Avro提供了:

  • 豐富的資料結構

  • 可壓縮、快速的二進位制資料格式

  • 一個用來儲存持久化資料的容器檔案

  • 遠端過程呼叫

  • 與動態語言的簡單整合,程式碼生成不需要讀取或寫入資料檔案,也不需要使用或實現RPC協議。程式碼生成是一種可選的優化,只值得在靜態型別語言中實現。


基於以上這些優點,avro在hadoop體系中被廣泛使用。除此之外,在hudi、iceberg中也都有用到avro作為元資料資訊的儲存格式。

【schema】

Avro依賴"schema"(模式)來實現資料結構的定義,schema通過json物件來進行描述表示,具體表現為:

  • 一個json字串命名一個定義的型別

  • 一個json物件,其格式為`{"type":"typeName" ...attributes...}`,其中`typeName`為原始型別名稱或複雜型別名稱。

  • 一個json陣列,表示嵌入型別的聯合



schema中的型別由原始型別(也就是基本型別)(null、boolean、int、long、float、double、bytes和string)和複雜型別(record、enum、array、map、union和fixed)組成。

1、原始型別

原始型別包括如下幾種:

  • null:沒有值

  • boolean:布林型別的值

  • int:32位整形

  • long:64位整形

  • float:32位浮點

  • double:64位浮點

  • bytes:8位無符號型別

  • string:unicode字符集序列


原始型別沒有指定的屬性值,原始型別的名稱也就是定義的型別的名稱,因此,schema中的"string"等價於{"type":"string"}。

2、複雜型別

Avro支援6種複雜型別:records、enums、arrays、maps、unions和fixed。

1)Records

reocrds使用型別名稱"record",並支援以下屬性

  • name:提供記錄名稱的json字串(必選)

  • namespace:限定名稱的json字串

  • doc:一個json字串,為使用者提供該模式的說明(可選)

  • aliases:字串的json陣列,為該記錄提供備用名稱

  • fields:一個json陣列,羅列所有欄位(必選),每個欄位又都是一個json物件,幷包含如下屬性:

    • name:欄位的名稱(必選)

    • doc:欄位的描述(可選)

    • type:一個schema,定義如上

    • default:欄位的預設值

    • order:指定欄位如何影響記錄的排序順序,有效值為`"ascending"`(預設值)、"descending"和"ignore"。

    • aliases:別名


一個簡單示例:

{
    "type": "record",
    "name": "LongList",
    "aliases": ["LinkedLongs"],
    "fields", [
        {"name": "value", "type": "long"},
        {"name": "next", "type": ["null", "LongList"]}
    ]
}

2)Enums

Enum使用型別名稱"enum",並支援以下屬性

  • name:提供記錄名稱的json字串(必選)

  • namespace:限定名稱的json字串

  • aliases:字串的json陣列,為該記錄提供備用名稱

  • doc:一個json字串,為使用者提供該模式的說明(可選)

  • symbols:一個json陣列,以json字串的形式列出符號。在列舉中每個符號必須唯一,不能重複,每個符號都必須匹配正則表示式"[A-Za-z_][A-Za-z0-9_]*"。

  • default:該列舉的預設值。

示例:

{
    "type": "enum",
    "name": "Suit",
    "symbols": ["SPADES", "HEARTS", "DIAMONDS", "CLUBS"]
}

3) Arrays

  • item:陣列中元素的schema

一個例子:宣告一個value為string的array

{
    "type": "array",
    "items": "string",
    "default": []
}

4)Maps

  • values:map的值(value)的schema,其key被假定為字串

一個例子:宣告一個value為long型別,(key型別為string)的map

{
    "type": "map",
    "values": "long",
    "default": {}
}

5)Unions

聯合使用json陣列表示,例如[null, "test"]宣告一個模式,它可以是空值或字串。

需要注意的是:當為union型別的欄位指定預設值時,預設值的型別必須與union第一個元素匹配,因此,對於包含"null"的union,通常先列出"null",因為此型別的union的預設值通常為空。

另外, union不能包含多個相同型別的schema,型別為record、fixed和eum除外。

6)Fixed

Fixed使用型別名稱"fixed"並支援以下屬性:

  • name:提供記錄名稱的json字串(必選)

  • namespace:限定名稱的json字串

  • aliases:字串的json陣列,為該記錄提供備用名稱

  • doc:一個json字串,為使用者提供該模式的說明(可選)

  • size:一個整數,指定每個值的位元組數(必須)

例如,16位元組的數可以宣告為:

{
    "type": "fixed",
    "name": "md5",
    "size": 16
}

【Avro的檔案儲存格式】

1、資料編碼

1)原始型別

對於null型別不寫入內容,即0位元組長度的內容表示;

對於boolean型別以1位元組的0或1來表示false或true;

對於int、long以zigzag的方式編碼寫入

對於float固定4位元組長度,先通過floatToIntBits轉換為32位整數,然後按小端編碼寫入

對於double固定8位元組長度,先通過doubleToLongBits轉換為64位整型,然後按小端編碼寫入

對於bytes先寫入長度(採用zigzag編碼寫入),然後是對應長度的二進位制資料內容

對於string同樣先寫入長度(採用zigzag編碼寫入),然後再寫入字串對應utf8的二進位制資料。

2)複雜型別

對於enums只需要將enum的值所在的Index作為結果進行編碼即可,例如,列舉值為["A","B","C","D"],那麼0就表示”A“,3表示"D"。

對於maps:被編碼為一系列的塊。每個塊由一個長整數的計數表示鍵值對的個數(採用zigzag編碼寫入),其後是多個鍵值對,計數為0的塊表示map的結束。每個元素按照各自的schema型別進行編碼。

對於arrays與map類似,同樣被編碼為一系列的塊,每個塊包含一個長整數的計數,計數後跟具體的陣列項內容,最後以0計數的塊表示結束。陣列項中的每個元素按照各自的schema型別進行編碼。

對於unions先寫入long型別的計數表示每個value值的位置序號(從零開始),然後再對值按對應schema進行編碼。

對於records直接按照schema中的欄位順序來進行編碼

對於fixed:使用schema中定義的位元組數對例項進行編碼

2、儲存格式

在一個標準的avro檔案中,同時儲存了schema的資訊,以及對應的資料內容。具體格式由三部分組成:

  • 魔數

固定4位元組長度,內容為字元'O','b','j',以及版本號標識,通常為1

  • 元資料資訊

檔案的元資料屬性,包括schema、資料壓縮編碼方式等。整個元資料屬性以一個map的形式編碼儲存每個屬性都以一個KV的形式儲存屬性名對應key,屬性值對應value,並以位元組陣列的形式儲存最後以一個固定16位元組長度的隨機字串標識元資料的結束

  • 資料內容

而資料內容則由一個或多個數據塊構成每個資料塊的最前面是一個long型(按照zigzag編碼儲存)的計數表示該資料塊中實際有多少條資料,後面再跟一個long型的計數表示編碼後的(N條)資料的長度,隨後就是按照編碼進行儲存的一條條資料,在每個資料塊的最後都有一個16位元組長度的隨機字串標識塊的結束。

整體儲存內容如下圖所示:

3、儲存格式

我們通過一個實際例子來對照分析下。

首先定義schema的內容,具體為4個欄位的表,名稱(字串)、年齡(整型)、技能(陣列)、其他(map型別),詳細如下所示:

{
    "type":"record",
    "name":"person",
    "fields": [
        {
            "name": "name",
            "type": "string"
        },
        {
            "name": "age",
            "type": "int"
        },
        {
            "name": "skill",
            "type": {
                "type":"array",
                "items": "string"
            }
        },
        {
            "name": "other",
            "type": {
                "type": "map",
                "values": "string"
            }
        }
    ]
}

再按照上面的schema定義兩條資料(person.json):

{"name":"hncscwc","age":20,"skill":["hadoop","flink","spark","kafka"],"other":{"interests":"basketball"}}
{"name":"tom","age":18, "skill":["java","scala"],"other":{}}

通過avro-tools可以生成一個avro檔案:

java -jar avro-tools-1.7.4.jar fromjson --schema-file person.avsc person.json > person.avro

通過二進位制的方式檢視生成的avro檔案內容:

另外,對於一個已存在的檔案,也可以通過avro-tools工具檢視schema內容、資料內容。

[root@localhost avro]$ java -jar avro-tools-1.7.4.jar getschema ./person.avro
{
  "type" : "record",
  "name" : "person",
  "fields" : [ {
    "name" : "name",
    "type" : "string"
  }, {
    "name" : "age",
    "type" : "int"
  }, {
    "name" : "skill",
    "type" : {
      "type" : "array",
      "items" : "string"
    }
  }, {
    "name" : "other",
    "type" : {
      "type" : "map",
      "values" : "string"
    }
  } ]
}
[root@localhost avro]$ java -jar avro-tools-1.7.4.jar tojson ./person.avro
{"name":"hncscwc","age":20,"skill":["hadoop","flink","spark","kafka"],"other":{"interests":"basketball"}}
{"name":"tom","age":18,"skill":["java","scala"],"other":{}}

【小結】

本文對avro的格式定義、編碼方式、以及實際儲存的檔案格式進行了詳細說明,最後也以一個實際例子進行了對照說明。另外, 在官網中還涉及rpc的使用、mapreduce的使用,這裡就沒有展開說明,有興趣的可移步官網進行查閱。

好了,這就是本文的全部內容,如果覺得本文對您有幫助,請點贊+轉發,如果覺得有不正確的地方,也可以拍磚指點,最後,歡迎加我微信交流~

本文分享自微信公眾號 - 陳猿解碼(gh_383bc7486c1a)。
如有侵權,請聯絡 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。