Avro簡介及Java運用
Avro 是一種遠端過程呼叫和資料序列化框架,是在Apache的Hadoop專案之內開發的。它使用JSON來定義資料型別和通訊協議,使用壓縮二進位制格式來序列化資料。它主要用於Hadoop,它可以為持久化資料提供一種序列化格式,Avro是一個數據序列化的系統。可以將資料結構或物件轉化成便於儲存或傳輸的格式。Avro設計之初就用來支援資料密集型應用,適合於遠端或本地大規模資料的儲存和交換。
序列化及反序列化概念
- 把物件轉換為位元組序列的過程稱為物件的序列化 。
- 把位元組序列恢復為物件的過程稱為物件的反序列化 。
物件的序列化主要有兩種用途:
- 把物件的位元組序列永久地儲存到硬碟上,通常存放在一個檔案中
- 在網路上傳送物件的位元組序列。
在很多應用中,需要對某些物件進行序列化,讓它們離開記憶體空間,入住物理硬碟,以便長期儲存。比如最常見的是Web伺服器中的Session物件,當有 10萬用戶併發訪問,就有可能出現10萬個Session物件,記憶體可能吃不消,於是Web容器就會把一些seesion先序列化到硬碟中,等要用了,再把儲存在硬碟中的物件還原到記憶體中。
當兩個程序在進行遠端通訊時,彼此可以傳送各種型別的資料。無論是何種型別的資料,都會以二進位制序列的形式在網路上傳送。傳送方需要把這個Java物件轉換為位元組序列,才能在網路上傳送;接收方則需要把位元組序列再恢復為Java物件。
Avro特點
-
豐富的資料結構型別;
-
快速可壓縮的二進位制資料形式,對資料二進位制序列化後可以節約資料儲存空間和網路傳輸頻寬;
-
儲存持久資料的檔案容器;
-
可以實現遠端過程呼叫RPC;
-
簡單的動態語言結合功能。
資料型別
基礎資料型別
Type | Description | Schema |
---|---|---|
null | the absence of a value | “null” |
boolean | a binary value | “boolean” |
int | 32-bit signed integer | “int” |
long | 64-bit signed integer | “long” |
float | Single-precision (32-bit) IEEE 754 floating-point number | “float” |
double | Double-precision (64-bit) IEEE 754 floating-point number | “double” |
bytes | Sequence of 8-bit unsigned bytes | “bytes” |
string | Sequence of Unicode characters | “string” |
複雜資料型別
Avro提供了6種複雜型別。分別是Record,Enum,Array,Map,Union和Fixed。
Record
Record是一個任意型別的命名欄位的集合
支援的屬性設定:
- name:record型別的名字(必填)
- namespace:名稱空間(可選)
- doc:這個型別的文件說明(可選)
- aliases:record型別的別名,是個字串陣列(可選)
-
fields:record型別中的欄位,是個物件陣列(必填)。每個欄位需要以下屬性:
- name:欄位名字(必填)
- doc:欄位說明文件(可選)
- type:一個schema的json物件或者一個型別名字(必填)
- default:預設值(可選)
- order:排序(可選),只有3個值ascending(預設),descending或ignore
- aliases:別名,字串陣列(可選)
一個Record型別例子,定義一個元素型別是Long的連結串列:
{ "type": "record", "name": "LongList", "aliases": ["LinkedLongs"], // old name for this "fields" : [ {"name": "value", "type": "long"}, // each element has a long {"name": "next", "type": ["null", "LongList"]} // optional next element ] }
Enum
列舉型別的型別名字是”enum”,還支援其它屬性的設定:
- name:列舉型別的名字(必填)
- namespace:名稱空間(可選)
- aliases:字串陣列,別名(可選)
- doc:說明文件(可選)
- symbols:字串陣列,所有的列舉值(必填),不允許重複資料。
一個列舉型別的例子:
{ "type": "enum", "name": "Suit", "symbols" : ["SPADES", "HEARTS", "DIAMONDS", "CLUBS"] }
Array
陣列型別的型別名字是”array”並且只支援一個屬性:
items:陣列元素的schema
一個數組例子:
{ "type": "array", "items": "string" }
Map
Map型別的型別名字是”map”並且只支援一個屬性:
values:map值的schema
Map的key必須是字串。
一個Map例子:
{ "type": "map", "values": "long" }
Union
組合型別,表示各種型別的組合,使用陣列進行組合。比如[“null”, “string”]
表示型別可以為null或者string。
如以下例子
{ "type": "record", "namespace": "com.example", "name": "FullName", "fields": [ { "name": "first", "type": ["null", "string"] }, { "name": "last", "type": "string", "default" : "Doe" } ] }
組合型別的預設值是看組合型別的第一個元素,因此如果一個組合型別包括null型別,那麼null型別一般都會放在第一個位置,這樣子的話這個組合型別的預設值就是null。
組合型別中不允許同一種類型的元素的個數不會超過1個,除了record,fixed和enum。比如組合類中有2個array型別或者2個map型別,這是不允許的。
組合型別不允許巢狀組合型別。
Fixed
混合型別的型別名字是fixed,支援以下屬性:
- name:名字(必填)
- namespace:名稱空間(可選)
- aliases:字串陣列,別名(可選)
- size:一個整數,表示每個值的位元組數(必填)
比如16個位元組數的fixed型別例子如下:
{ "type": "fixed", "size": 16, "name": "md5" }
java實踐
配置
引入pom.xml
<dependency> <groupId>org.apache.avro</groupId> <artifactId>avro</artifactId> <version>1.8.2</version> </dependency>
下載avro-tool
avro-tools-1.8.2.jar 該jar包主要使用者將定義好的schema檔案生成對應的java檔案
下載地址schema
Avro依賴模式(Schema)來實現資料結構定義。可以把模式理解為Java的類,它定義每個例項的結構,可以包含哪些屬性。可以根據類來產生任意多個例項物件。對例項序列化操作時必須需要知道它的基本結構,也就需要參考類的資訊。這裡,根據模式產生的Avro物件類似於類的例項物件。每次序列化/反序列化時都需要知道模式的具體結構。所以,在Avro可用的一些場景下,如檔案儲存或是網路通訊,都需要模式與資料同時存在。Avro資料以模式來讀和寫(檔案或是網路),並且寫入的資料都不需要加入其它標識,這樣序列化時速度快且結果內容少。由於程式可以直接根據模式來處理資料,所以Avro更適合於指令碼語言的發揮。
定義schema
avro的schema是json格式,支援前文所描述的型別。
下面是一個schema例子 User.avsc
{ "type": "record", "name": "User", "fields": [ { "name": "name", "type": "string", "doc":"姓名" }, { "name": "favorite_number", "type": [ "int", "null" ] }, { "name": "favorite_color", "type": [ "string", "null" ] } ] }
編譯schema
使用avro-tools-1.8.2.jar 編譯
編譯命令格式如下:
java -jar avro-tools-1.8.2.jar compile schema <filename.avsc> <outputpath>
序列化與反序列化
avro的序列化和反序列化,有兩種方法。第一種是需要利用schema生成實體類,另外一種只需要指定schema並使用GenericRecord作為實體。
生成實體類User
編譯schema至java專案src目錄
序列化
AvroSerialize.java
import java.io.*; import org.apache.avro.file.*; import org.apache.avro.io.*; import org.apache.avro.specific.*; public class AvroSerialize { public static void main(String args[]) throws IOException { // 新建3個實體類 User user1 = new User(); user1.setName("Alyssa"); user1.setFavoriteNumber(256); User user2 = new User("Ben", 7, "red"); User user3 = User.newBuilder() .setName("Charlie") .setFavoriteColor("blue") .setFavoriteNumber(null) .build(); DatumWriter<User> userDatumWriter = new SpecificDatumWriter<User>(User.class); DataFileWriter<User> dataFileWriter = new DataFileWriter<User>(userDatumWriter); dataFileWriter.create(user1.getSchema(), new File("./src/main/avro/user.avro")); dataFileWriter.append(user1); dataFileWriter.append(user2); dataFileWriter.append(user3); dataFileWriter.close(); } }
反序列化
import java.io.*; import org.apache.avro.file.*; import org.apache.avro.io.*; import org.apache.avro.specific.*; public class AvroDeserialize { public static void main(String args[]) { DatumReader<User> reader = new SpecificDatumReader<>(User.class); DataFileReader<User> fileReader = null; try { fileReader = new DataFileReader<User>(new File("./src/main/avro/user.avro"), reader); User user = null; while (fileReader.hasNext()) { user = fileReader.next(user); System.out.println(user); } } catch (Exception e) { e.printStackTrace(); } } }
輸出
{“name”: “Alyssa”, “favorite_number”: 256, “favorite_color”: null} {“name”: “Ben”, “favorite_number”: 7, “favorite_color”: “red”} {“name”: “Charlie”, “favorite_number”: null, “favorite_color”: “blue”}
使用GenericRecord
序列化
AvroGenericDeserializa.java
package avro; import java.io.*; import org.apache.avro.Schema; import org.apache.avro.file.*; import org.apache.avro.generic.*; import org.apache.avro.io.*; public class AvroGenericDeserializa { public static void main(String args[]) throws IOException { Schema schema = new Schema.Parser().parse(new File("./src/main/avro/User.avsc")); GenericRecord user1 = new GenericData.Record(schema); user1.put("name", "Alyssa"); user1.put("favorite_number", 256); GenericRecord user2 = new GenericData.Record(schema); user2.put("name", "Ben"); user2.put("favorite_number", 7); user2.put("favorite_color", "red"); File file = new File("./src/main/avro/user_generic.avro"); DatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<GenericRecord>(schema); DataFileWriter<GenericRecord> dataFileWriter = new DataFileWriter<GenericRecord>(datumWriter); dataFileWriter.create(schema, file); dataFileWriter.append(user1); dataFileWriter.append(user2); dataFileWriter.close(); } }
反序列化
AvroGenericSerialize.java
import java.io.*; import org.apache.avro.Schema; import org.apache.avro.file.*; import org.apache.avro.generic.*; import org.apache.avro.io.*; public class AvroGenericSerialize { public static void main(String args[]) throws IOException { Schema schema = new Schema.Parser().parse(new File("./src/main/avro/User.avsc")); File file = new File("./src/main/avro/user_generic.avro"); DatumReader<GenericRecord> datumReader = new GenericDatumReader<GenericRecord>(schema); DataFileReader<GenericRecord> dataFileReader = new DataFileReader<GenericRecord>(file, datumReader); GenericRecord user = null; while (dataFileReader.hasNext()) { user = dataFileReader.next(user); System.out.println(user); } } }
輸出
{“name”: “Alyssa”, “favorite_number”: 256, “favorite_color”: null} {“name”: “Ben”, “favorite_number”: 7, “favorite_color”: “red”}