由於Avro 將在MapReduce V2(YARN)上被使用,所以小弟也來研究一下Avro的一些觀念與實作方式囉^_^~,當然MapReduce V1也可以用到拉。
做為Hadoop資料的序列化(Serialize)與反序列化(Deserialize),比起傳統的Writable,Avro使用來改進Writable的缺點: 可攜性的問題(Writable序列化後的資料無法讓其他程式語言使用)。
當然不僅Avro做得到,還有像是Apache的Thrift、google的protocal buffer也都可以,我個人認為(純粹個人認為),Doug先生使用Avro而不使用其他的方式來取代傳統的Writable或許是因為他對Avro的掌握程度遠比其他的方式還高。
Avro的幾個重點 :
- Schema(綱要) 是用JSON來寫。
- 嚴格定義二進位格式,也是因為這樣讓其與其他程式語言的聯繫門檻降低。
- 擁有豐富的schema解析,資料寫入與讀取的schema可以不同,這讓schema可以一直演變,這非常屌喔!~
- 規範了"物件容器格式"用來儲存物件序列,這個跟hadoop的循序檔很類似,重點是"Avro資料檔案支援壓縮與分割",可成為MapReduce的資料輸入,這才是屌到爆的地方。
接下來就是要介紹一下Avro的資料型別與綱要。
Avro的資料型別: "基本型別",舉個例子
型別 schema
null "null"
boolean "boolean"
int "int"
string "string"
Avro的資料型別: "複雜型別",舉個例子
型別 schema
record {
"type": "record"
"name": "test"
"fields": [
{"name": "Tony", "type": "string"},
{"name": "Emma", "type": "string"}
]
}
因為Avro是用JSON來寫,所以在寫入或讀取的時候必須告知型別,由上述簡單的帶出,接下來就是說明一下Avro如何做序列化與反序列化的部分。
Avro 有三種方式來達成
- 1. 使用code generation(可參考avro的doc,http://apache.cdpa.nsysu.edu.tw/avro/avro-1.7.3/)。
- 2. 不需使用code generation,而是用Schema parser(照理說這個應該是最常用的),又分成下面2-1與2-2兩種方式來做。
- 2-1. 在memory中做序列化與反序列化。
- 2-2. 序列化與反序列化於硬碟的操作。
- 3. 使用Maven(這個部分我沒有用過,有興趣的同學可以用用看唷^.^)。
================無聊分隔線====================
A. code generation
1. 在做Avro的序列化之前,必須先define好schema,這裡直接拿書上的例子來跑!~。
{"namespace": "example.avro",
"type": "record",
"name": "User",
"fields": [
{"name": "name", "type": "string"},
{"name": "favorite_number", "type": ["int", "null"]},
{"name": "favorite_color", "type": ["string", "null"]}
]
}
註:order的部分乃是用來排序使用,預計將於觀念篇(2) + MapReduce中會用到。
2. compile schema
java -jar /path/to/avro-tools-1.7.3.jar compile schema <schema file> <destination>
ex: java -jar /path/to/avro-tools-1.7.3.jar compile schema user.avsc . (貼心小語,destination是一個 . ,也就是當前路徑,別忘囉)
compile後會在當前路徑產生一個目錄 example/avro/User.java (在這裡就能說明為什麼取名是code generation)。
3. 接下來就是把上述example/avro/User.java加到自己的java包中,不然下面的例子會build不過。
User user1 = new User();
user1.setName("Alyssa");
user1.setFavoriteNumber(256);
// Leave favorite color null
// Alternate constructor
User user2 = new User("Ben", 7, "red");
// Construct via builder
User user3 = User.newBuilder().setName("Charlie").setFavoriteColor("blue").setFavoriteNumber(null).build();
4. 序列化後的資料放到硬碟。
// Serialize user1 and user2 to disk
File file = new File("users.avro");
DatumWriter<User> userDatumWriter = new SpecificDatumWriter<User>(User.class);
DataFileWriter<User> dataFileWriter = new DataFileWriter<User>(userDatumWriter);
dataFileWriter.create(user1.getSchema(), new File("users.avro"));
dataFileWriter.append(user1);
dataFileWriter.append(user2);
dataFileWriter.append(user3);
dataFileWriter.close();
5. 反序列化硬碟中的資料
// Deserialize Users from disk
DatumReader<User> userDatumReader = new SpecificDatumReader<User>(User.class);
DataFileReader<User> dataFileReader = new DataFileReader<User>(file, userDatumReader);
User user = null;
while (dataFileReader.hasNext()) {
// Reuse user object by passing it to next(). This saves us from
// allocating and garbage collecting many objects for files with
// many items.
user = dataFileReader.next(user);
System.out.println(user);
}
跑上面的code做反序列化後可以看到下述結果
{"name": "Alyssa", "favorite_number": 256, "favorite_color": null}
{"name": "Ben", "favorite_number": 7, "favorite_color": "red"}
{"name": "Charlie", "favorite_number": null, "favorite_color": "blue"}
好啦,到這邊我已經快吐了,code generation的方式看起來好不實際=0=,於下篇中將會介紹without code generation的做法,BTW. 這邊的做法是屬於2-2. 序列化與反序列化於硬碟的操作。