AVRO讲解
一、引言
1、?簡介
Avro是Hadoop中的一個子項目,也是Apache中一個獨立的項目,Avro是一個基于二進(jìn)制數(shù)據(jù)傳輸高性能的中間件。在Hadoop的其他項目中例如HBase(Ref)和Hive(Ref)的Client端與服務(wù)端的數(shù)據(jù)傳輸也采用了這個工具。Avro是一個數(shù)據(jù)序列化的系統(tǒng)。Avro 可以將數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)化成便于存儲或傳輸?shù)母袷健vro設(shè)計之初就用來支持?jǐn)?shù)據(jù)密集型應(yīng)用,適合于遠(yuǎn)程或本地大規(guī)模數(shù)據(jù)的存儲和交換。
2、?特點
? 豐富的數(shù)據(jù)結(jié)構(gòu)類型;
? 快速可壓縮的二進(jìn)制數(shù)據(jù)形式,對數(shù)據(jù)二進(jìn)制序列化后可以節(jié)約數(shù)據(jù)存儲空間和網(wǎng)絡(luò)傳輸帶寬;
? 存儲持久數(shù)據(jù)的文件容器;
? 可以實現(xiàn)遠(yuǎn)程過程調(diào)用RPC;
? 簡單的動態(tài)語言結(jié)合功能。
avro支持跨編程語言實現(xiàn)(C, C++, C#,Java, Python, Ruby, PHP),類似于Thrift,但是avro的顯著特征是:avro依賴于模式,動態(tài)加載相關(guān)數(shù)據(jù)的模式,Avro數(shù)據(jù)的讀寫操作很頻繁,而這些操作使用的都是模式,這樣就減少寫入每個數(shù)據(jù)文件的開銷,使得序列化快速而又輕巧。這種數(shù)據(jù)及其模式的自我描述方便了動態(tài)腳本語言的使用。當(dāng)Avro數(shù)據(jù)存儲到文件中時,它的模式也隨之存儲,這樣任何程序都可以對文件進(jìn)行處理。如果讀取數(shù)據(jù)時使用的模式與寫入數(shù)據(jù)時使用的模式不同,也很容易解決,因為讀取和寫入的模式都是已知的。
| New schema | Writer | Reader | Action |
| Added field | Old | New | The reader uses the default value of the new field, since it is not written by the writer. |
| ? | New | Old | The reader does not know about the new field written by the writer, so it is ignored (projection). |
| Removed field | Old | New | The reader ignores the removed field (projection). |
| ? | New | Old | The removed field is not written by the writer. If the old schema had a default defined for the field, the reader uses this; otherwise, it gets an error. In this case, it is best to update the reader’s schema, either at the same time as or before the writer’s. |
Avro和動態(tài)語言結(jié)合后,讀/寫數(shù)據(jù)文件和使用RPC協(xié)議都不需要生成代碼,而代碼生成作為一種可選的優(yōu)化只需要在靜態(tài)類型語言中實現(xiàn)。
Avro依賴于模式(Schema)。通過模式定義各種數(shù)據(jù)結(jié)構(gòu),只有確定了模式才能對數(shù)據(jù)進(jìn)行解釋,所以在數(shù)據(jù)的序列化和反序列化之前,必須先確定模式的結(jié)構(gòu)。正是模式的引入,使得數(shù)據(jù)具有了自描述的功能,同時能夠?qū)崿F(xiàn)動態(tài)加載,另外與其他的數(shù)據(jù)序列化系統(tǒng)如Thrift相比,數(shù)據(jù)之間不存在其他的任何標(biāo)識,有利于提高數(shù)據(jù)處理的效率。????
二、技術(shù)要領(lǐng)
1、?類型
數(shù)據(jù)類型標(biāo)準(zhǔn)化的意義:一方面使不同系統(tǒng)對相同的數(shù)據(jù)能夠正確解析,另一方面,數(shù)據(jù)類型的標(biāo)準(zhǔn)定義有利于數(shù)據(jù)序列化/反序列化。
簡單的數(shù)據(jù)類型:Avro定義了幾種簡單數(shù)據(jù)類型,下表是其簡單說明:
| 類型 | 說明 |
| null | no value |
| boolean | a binary value |
| int | 32-bit signed integer |
| long | 64-bit signed integer |
| float | single precision (32-bit) IEEE 754 floating-point number |
| double | double precision (64-bit) IEEE 754 floating-point number |
| bytes | sequence of 8-bit unsigned bytes |
| string | unicode character sequence |
簡單數(shù)據(jù)類型由類型名稱定義,不包含屬性信息,例如字符串定義如下:
{"type": "string"}
復(fù)雜數(shù)據(jù)類型:Avro定義了六種復(fù)雜數(shù)據(jù)類型,每一種復(fù)雜數(shù)據(jù)類型都具有獨特的屬性,下表就每一種復(fù)雜數(shù)據(jù)類型進(jìn)行說明。
| 類型 | 屬性 | 說明 |
| Records | type name | record |
| name | a JSON string providing the name of the record (required). | |
| namespace | a JSON string that qualifies the name(optional). | |
| doc | a JSON string providing documentation to the user of this schema (optional). | |
| aliases | a JSON array of strings, providing alternate names for this record (optional). | |
| fields | a JSON array, listing fields (required). | |
| ??????????? name | a JSON string. | |
| ??????????? type | a schema/a string of defined record. | |
| ??????????? default | a default value for field when lack. | |
| ??????????? order | ordering of this field. | |
| Enums | type name | enum |
| ? | name | a JSON string providing the name of the enum (required). |
| namespace | a JSON string that qualifies the name. | |
| doc | a JSON string providing documentation to the user of this schema (optional). | |
| aliases | a JSON array of strings, providing alternate names for this enum (optional) | |
| symbols | a JSON array, listing symbols, as JSON strings (required). All symbols in an enum must be unique. | |
| Arrays | type name | array |
| ? | items | the schema of the array’s items. |
| Maps | type name | map |
| ? | values | the schema of the map’s values. |
| Fixed | type name | fixed |
| ? | name | a string naming this fixed (required). |
| ? | namespace | a string that qualifies the name. |
| ? | aliases | a JSON array of strings, providing alternate names for this enum (optional). |
| ? | size | an integer, specifying the number of bytes per value (required). |
| Unions | ? | a JSON arrays |
每一種復(fù)雜數(shù)據(jù)類型都含有各自的一些屬性,其中部分屬性是必需的,部分是可選的。
這里需要說明Record類型中field屬性的默認(rèn)值,當(dāng)Record Schema實例數(shù)據(jù)中某個field屬性沒有提供實例數(shù)據(jù)時,則由默認(rèn)值提供,具體值見下表。Union的field默認(rèn)值由Union定義中的第一個Schema決定。
| avro type | json type | example |
| null | null | null |
| boolean | boolean | true |
| int,long | integer | 1 |
| float,double | number | 1.1 |
| bytes | string | "\u00FF" |
| string | string | "foo" |
| record | object | {"a": 1} |
| enum | string | "FOO" |
| array | array | [1] |
| map | object | {"a": 1} |
| fixed | string | "\u00ff" |
2、?序列化/反序列化
Avro指定兩種數(shù)據(jù)序列化編碼方式:binary encoding 和Json encoding。使用二進(jìn)制編碼會高效序列化,并且序列化后得到的結(jié)果會比較小;而JSON一般用于調(diào)試系統(tǒng)或是基于WEB的應(yīng)用。
binary encoding規(guī)則如下:
1、? 簡單數(shù)據(jù)類型
| Type | Encoding | Example |
| null | Zero bytes | Null |
| boolean | A single byte | {true:1, false:0} |
| int/long | variable-length zig-zag coding | ? |
| float | 4 bytes | Java's floatToIntBits |
| double | 8 bytes | Java's doubleToLongBits |
| bytes | a long followed by that many bytes of data | ? |
| string | a long followed by that many bytes of UTF-8 encoded character data | “foo”:{3,f,o,o} 06 66 6f 6f |
2、? 復(fù)雜數(shù)據(jù)類型
| Type | encoding |
| Records | encoded just the concatenation of the encodings of its?fields |
| Enums | a int representing the zero-based?position?of the symbol in the schema |
| Arrays | encoded as series of blocks. A block with count 0 indicates the end of the array. block:{long,items} |
| Maps | encoded as series of blocks. A block with count 0 indicates the end of the map. block:{long,key/value pairs}. |
| Unions | encoded by first writing a long value indicating the zero-based position within the union of the schema of its value. The value is then encoded per the indicated schema within the union. |
| fixed | encoded using number of bytes declared in the schema |
實例:
?records
{
"type":"record",
"name":"test",
"fields" : [
{"name": "a","type": "long"},
{"name": "b","type": "string"}
]
}
假設(shè):a=27b=”foo” (encoding:36(27), 06(3), 66("f"), 6f("o"))
binary encoding:3606 66 6f 6f
?enums
{"type": "enum","name": "Foo", "symbols": ["A","B", "C", "D"] }
“D”(encoding: 06(3))
binary encoding: 06
?arrays
{"type": "array","items": "long"}
設(shè):{3, 27 } (encoding:04(2), 06(3), 36(27) )
binary encoding:0406 36 00
?maps
設(shè):{("a":1), ("b":2) } (encoding:61(“a”), 62(“b”), 02(1), 04(2))
binary encoding:0261 02 02 62 04
?unions
["string","null"]
設(shè):(1)null; (2) “a”
binary encoding:
(1) 02;說明:02代表null在union定義中的位置1;
(2) 00 02 61;說明:00為string在union定義的位置,02 61為”a”的編碼。
?
圖1表示的是Avro本地序列化和反序列化的實例,它將用戶定義的模式和具體的數(shù)據(jù)編碼成二進(jìn)制序列存儲在對象容器文件中,例如用戶定義了包含學(xué)號、姓名、院系和電話的學(xué)生模式,而Avro對其進(jìn)行編碼后存儲在student.db文件中,其中存儲數(shù)據(jù)的模式放在文件頭的元數(shù)據(jù)中,這樣讀取的模式即使與寫入的模式不同,也可以迅速地讀出數(shù)據(jù)。假如另一個程序需要獲取學(xué)生的姓名和電話,只需要定義包含姓名和電話的學(xué)生模式,然后用此模式去讀取容器文件中的數(shù)據(jù)即可。
????????????????????????????? ??????????????????????????? 圖表 1
?
3、?模式Schema
Schema通過JSON對象表示。Schema定義了簡單數(shù)據(jù)類型和復(fù)雜數(shù)據(jù)類型,其中復(fù)雜數(shù)據(jù)類型包含不同屬性。通過各種數(shù)據(jù)類型用戶可以自定義豐富的數(shù)據(jù)結(jié)構(gòu)。
Schema由下列JSON對象之一定義:
1. JSON字符串:命名
2. JSON對象:{“type”: “typeName” …attributes…}
3. JSON數(shù)組:Avro中Union的定義
舉例:
{"namespace": "example.avro",
?"type":"record",
?"name":"User",
?"fields": [
???? {"name":"name", "type": "string"},
???? {"name":"favorite_number",?"type": ["int", "null"]},
???? {"name":"favorite_color", "type": ["string","null"]}
?]
}
4、?排序
Avro為數(shù)據(jù)定義了一個標(biāo)準(zhǔn)的排列順序。比較在很多時候是經(jīng)常被使用到的對象之間的操作,標(biāo)準(zhǔn)定義可以進(jìn)行方便有效的比較和排序。同時標(biāo)準(zhǔn)的定義可以方便對Avro的二進(jìn)制編碼數(shù)據(jù)直接進(jìn)行排序而不需要反序列化。
只有當(dāng)數(shù)據(jù)項包含相同的Schema的時候,數(shù)據(jù)之間的比較才有意義。數(shù)據(jù)的比較按照Schema深度優(yōu)先,從左至右的順序遞歸的進(jìn)行。找到第一個不匹配即可終止比較。
兩個擁有相同的模式的項的比較按照以下規(guī)則進(jìn)行:
?????? null:總是相等。
int,long,float:按照數(shù)值大小比較。
boolean:false在true之前。
string:按照字典序進(jìn)行比較。
bytes,fixed:按照byte的字典序進(jìn)行比較。
array:按照元素的字典序進(jìn)行比較。
enum:按照符號在枚舉中的位置比較。
record:按照域的字典序排序,如果指定了以下屬性:
“ascending”,域值的順序不變。
“descending”,域值的順序顛倒。
“ignore”,排序的時候忽略域值。
map:不可進(jìn)行比較。
?
5、?對象容器文件
Avro定義了一個簡單的對象容器文件格式。一個文件對應(yīng)一個模式,所有存儲在文件中的對象都是根據(jù)模式寫入的。對象按照塊進(jìn)行存儲,塊可以采用壓縮的方式存儲。為了在進(jìn)行mapreduce處理的時候有效的切分文件,在塊之間采用了同步記號。一個文件可以包含任意用戶定義的元數(shù)據(jù)。
一個文件由兩部分組成:文件頭和一個或者多個文件數(shù)據(jù)塊。
文件頭:
?四個字節(jié),ASCII‘O’,‘b’,‘j’,1。
?文件元數(shù)據(jù),用于描述Schema。
?16字節(jié)的文件同步記號。
?其中,文件元數(shù)據(jù)的格式為:
???????????????????????i.?????????????值為-1的長整型,表明這是一個元數(shù)據(jù)塊。
?????????????????????ii.?????????????標(biāo)識塊長度的長整型。
???????????????????iii.?????????????標(biāo)識塊中key/value對數(shù)目的長整型。
????????????????????iv.?????????????每一個key/value對的string key和bytesvalue。
??????????????????????v.?????????????標(biāo)識塊中字節(jié)總數(shù)的4字節(jié)長的整數(shù)。
文件數(shù)據(jù)塊:
數(shù)據(jù)是以塊結(jié)構(gòu)進(jìn)行組織的,一個文件可以包含一個或者多個文件數(shù)據(jù)塊。
?表示文件中塊中對象數(shù)目的長整型。
?表示塊中數(shù)據(jù)序列化后的字節(jié)數(shù)長度的長整型。
?序列化的對象。
?16字節(jié)的文件同步記號。
當(dāng)數(shù)據(jù)塊的長度為0時即為文件數(shù)據(jù)塊的最后一個數(shù)據(jù),此后的所有數(shù)據(jù)被自動忽略。
下圖示對象容器文件的結(jié)構(gòu)分解及說明:
?
一個存儲文件由兩部分組成:頭信息(Header)和數(shù)據(jù)塊(Data Block)。而頭信息又由三部分構(gòu)成:四個字節(jié)的前綴,文件Meta-data信息和隨機(jī)生成的16字節(jié)同步標(biāo)記符。Avro目前支持的Meta-data有兩種:schema和codec。
codec表示對后面的文件數(shù)據(jù)塊(File Data Block)采用何種壓縮方式。Avro的實現(xiàn)都需要支持下面兩種壓縮方式:null(不壓縮)和deflate(使用Deflate算法壓縮數(shù)據(jù)塊)。除了文檔中認(rèn)定的兩種Meta-data,用戶還可以自定義適用于自己的Meta-data。這里用long型來表示有多少個Meta-data數(shù)據(jù)對,也是讓用戶在實際應(yīng)用中可以定義足夠的Meta-data信息。對于每對Meta-data信息,都有一個string型的key(需要以“avro.” 為前綴)和二進(jìn)制編碼后的value。對于文件中頭信息之后的每個數(shù)據(jù)塊,有這樣的結(jié)構(gòu):一個long值記錄當(dāng)前塊有多少個對象,一個long值用于記錄當(dāng)前塊經(jīng)過壓縮后的字節(jié)數(shù),真正的序列化對象和16字節(jié)長度的同步標(biāo)記符。由于對象可以組織成不同的塊,使用時就可以不經(jīng)過反序列化而對某個數(shù)據(jù)塊進(jìn)行操作。還可以由數(shù)據(jù)塊數(shù),對象數(shù)和同步標(biāo)記符來定位損壞的塊以確保數(shù)據(jù)完整性。
三、RPC實現(xiàn)
當(dāng)在RPC中使用Avro時,服務(wù)器和客戶端可以在握手連接時交換模式。服務(wù)器和客戶端有彼此全部的模式,因此相同命名字段、缺失字段和多余字段等信息之間通信中需要處理的一致性問題就可以容易解決。如圖2所示,協(xié)議中定義了用于傳輸?shù)南?#xff0c;消息使用框架后放入緩沖區(qū)中進(jìn)行傳輸,由于傳輸?shù)某跏季徒粨Q了各自的協(xié)議定義,因此即使傳輸雙方使用的協(xié)議不同所傳輸?shù)臄?shù)據(jù)也能夠正確解析。
圖表 2
Avro作為RPC框架來使用。客戶端希望同服務(wù)器端交互時,就需要交換雙方通信的協(xié)議,它類似于模式,需要雙方來定義,在Avro中被稱為消息(Message)。通信雙方都必須保持這種協(xié)議,以便于解析從對方發(fā)送過來的數(shù)據(jù),這也就是傳說中的握手階段。?
消息從客戶端發(fā)送到服務(wù)器端需要經(jīng)過傳輸層(Transport Layer),它發(fā)送消息并接收服務(wù)器端的響應(yīng)。到達(dá)傳輸層的數(shù)據(jù)就是二進(jìn)制數(shù)據(jù)。通常以HTTP作為傳輸模型,數(shù)據(jù)以POST方式發(fā)送到對方去。在 Avro中,它的消息被封裝成為一組緩沖區(qū)(Buffer),類似于下圖的模型:
如上圖,每個緩沖區(qū)以四個字節(jié)開頭,中間是多個字節(jié)的緩沖數(shù)據(jù),最后以一個空緩沖區(qū)結(jié)尾。這種機(jī)制的好處在于,發(fā)送端在發(fā)送數(shù)據(jù)時可以很方便地組裝不同數(shù)據(jù)源的數(shù)據(jù),接收方也可以將數(shù)據(jù)存入不同的存儲區(qū)。還有,當(dāng)往緩沖區(qū)中寫數(shù)據(jù)時,大對象可以獨占一個緩沖區(qū),而不是與其它小對象混合存放,便于接收方方便地讀取大對象。
對象容器文件是Avro的數(shù)據(jù)存儲的具體實現(xiàn),數(shù)據(jù)交換則由RPC服務(wù)提供,與對象容器文件類似,數(shù)據(jù)交換也完全依賴Schema,所以與Hadoop目前的RPC不同,Avro在數(shù)據(jù)交換之前需要通過握手過程先交換Schema。
1、 握手過程
握手的過程是確保Server和Client獲得對方的Schema定義,從而使Server能夠正確反序列化請求信息,Client能夠正確反序列化響應(yīng)信息。一般的,Server/Client會緩存最近使用到的一些協(xié)議格式,所以,大多數(shù)情況下,握手過程不需要交換整個Schema文本。
所有的RPC請求和響應(yīng)處理都建立在已經(jīng)完成握手的基礎(chǔ)上。對于無狀態(tài)的連接,所有的請求響應(yīng)之前都附有一次握手過程;對于有狀態(tài)的連接,一次握手完成,整個連接的生命期內(nèi)都有效。
具體過程:
Client發(fā)起HandshakeRequest,其中含有Client本身SchemaHash值和對應(yīng)Server端的Schema Hash值(clientHash!=null,clientProtocol=null, serverHash!=null)。如果本地緩存有serverHash值則直接填充,如果沒有則通過猜測填充。
Server用如下之一HandshakeResponse響應(yīng)Client請求:
(match=BOTH, serverProtocol=null,serverHash=null):當(dāng)Client發(fā)送正確的serverHash值且Server緩存相應(yīng)的clientHash。握手過程完成,之后的數(shù)據(jù)交換都遵守本次握手結(jié)果。
(match=CLIENT, serverProtocol!=null,serverHash!=null):當(dāng)Server緩存有Client的Schema,但是Client請求中ServerHash值不正確。此時Server發(fā)送Server端的Schema數(shù)據(jù)和相應(yīng)的Hash值,此次握手完成,之后的數(shù)據(jù)交換都遵守本次握手結(jié)果。
(match=NONE):當(dāng)Client發(fā)送的ServerHash不正確且Server端沒有Client Schema的緩存。這種情況下Client需要重新提交請求信息 (clientHash!=null,clientProtocol!=null, serverHash!=null),Server響應(yīng) (match=BOTH, serverProtocol=null,serverHash=null),此次握手過程完成,之后的數(shù)據(jù)交換都遵守本次握手結(jié)果。
握手過程使用的Schema結(jié)構(gòu)如下示。
{
"type":"record",
"name":"HandshakeRequest","namespace":"org.apache.avro.ipc",
"fields":[
{"name":"clientHash", "type": {"type": "fixed","name": "MD5", "size": 16}},
{"name":"clientProtocol", "type": ["null","string"]},
{"name":"serverHash", "type": "MD5"},
{"name":"meta", "type": ["null", {"type":"map", "values": "bytes"}]}
]
}
{
"type":"record",
"name":"HandshakeResponse", "namespace":"org.apache.avro.ipc",
"fields":[
{"name":"match","type": {"type": "enum","name": "HandshakeMatch",
"symbols":["BOTH", "CLIENT", "NONE"]}},
{"name":"serverProtocol", "type": ["null","string"]},
{"name":"serverHash","type": ["null", {"type":"fixed", "name": "MD5", "size": 16}]},
{"name":"meta","type": ["null", {"type":"map", "values": "bytes"}]}
]
}?
2、?消息幀格式
消息從客戶端發(fā)送到服務(wù)器端需要經(jīng)過傳輸層,它發(fā)送請求并接收服務(wù)器端的響應(yīng)。到達(dá)傳輸層的數(shù)據(jù)就是二進(jìn)制數(shù)據(jù)。通常以HTTP作為傳輸模型,數(shù)據(jù)以POST方式發(fā)送到對方去。在 Avro中消息首先分幀后被封裝成為一組緩沖區(qū)(Buffer)。
數(shù)據(jù)幀的格式如下:
?一系列Buffer:
1、4字節(jié)的Buffer長度
2、Buffer字節(jié)數(shù)據(jù)
?長度為0的Buffer結(jié)束數(shù)據(jù)幀?
3、?Call格式
一個調(diào)用由請求消息、結(jié)果響應(yīng)消息或者錯誤消息組成。請求和響應(yīng)包含可擴(kuò)展的元數(shù)據(jù),兩種消息都按照之前提出的方法分幀。
調(diào)用的請求格式為:
? 請求元數(shù)據(jù),一個類型值的映射。
? 消息名,一個Avro字符串。
? 消息參數(shù)。參數(shù)根據(jù)消息的請求定義序列化。
調(diào)用的響應(yīng)格式為:
? 響應(yīng)的元數(shù)據(jù),一個類型值的映射。
? 一字節(jié)的錯誤標(biāo)志位。
? 如果錯誤標(biāo)志為false,響應(yīng)消息,根據(jù)響應(yīng)的模式序列化。
如果錯誤標(biāo)志位true,錯誤消息,根據(jù)消息的錯誤聯(lián)合模式序列化。?
轉(zhuǎn)載于:https://www.cnblogs.com/livebetter/p/9480632.html
總結(jié)
- 上一篇: 跟原厂豆瓣工程师学Python,精通Py
- 下一篇: 华为什么时候发布鸿蒙手机,鸿蒙正式发布!