Avro簡介

概述

Apache Avro是一個獨立於編程語言的數據序列化系統,獨立於語言也就意味着可以被多種語言處理。Avro起源於Hadoop,目的是解決Hadoop Writable類型的不足:缺乏語言的可移植性。
Avro是基於schema(模式),這和protobuf、thrift沒什麼區別,在schema文件中(.avsc文件)中聲明數據類型或者protocol(RPC接口),那麼avro在read、write時將依據schema對數據進行序列化。因爲有了schema,那麼Avro的讀、寫操作將可以使用不同的平臺語言。Avro的schema是JSON格式,所以編寫起來也非常簡單、可讀性很好。
Avro提供:
豐富的數據結構類型
快速可壓縮的二進制數據形式
存儲持久化數據的文件容器
RPC
簡單的和動態語言結合,無論在讀寫數據文件還是實現RPC protocol時,由arvo schema產生代碼的過程都不是必須的,code generation是一個可選的優化,在使用靜態語言時產生代碼是值得的。avro依賴於模式,動態加載相關數據的模式,Avro數據的讀寫操作很頻繁,而這些操作使用的都是模式,這樣就減少寫入每個數據文件的開銷,使得序列化快速而又輕巧。這種數據及其模式的自我描述方便了動態腳本語言的使用。當Avro數據存儲到文件中時,它的模式也隨之存儲,這樣任何程序都可以對文件進行處理。

Avro數據類型和模式

Avro的基本類型

類型名稱 描述模式 示例
null 空值 “null”
bolean 二進制值 “boolean”
int 32位帶符號整數 “int”
long 64位帶符號整數 “long”
float 單精度(32位)IEEE754浮點數 “float”
double 雙精度(64位) IEEE754浮點數 “double”
bytes 8位無符號字節序列 “bytes”
string Unicode字符序列 “string”

Avro複雜類型

這裏寫圖片描述
這裏寫圖片描述

複雜類型註解

array:數組,和java中的數組沒有區別。其item屬性表示數組的item的類型。
map:跟java中的map一樣,只是key必須爲string(無法聲明key 的類型),“values”屬性聲明value的類型。
record:這個和java中的“class”有同等的意義,它支持如下屬性:
1)name:必要屬性,表示record的名稱,在java生成代碼時作爲類的名稱。
2)namespace:限定名,在java生成代碼時作爲package的名字。其中namespace + name最終構成record的全名。
3)doc:可選,文檔信息,備註信息。
4)aliases:別名
5) fields:field列表,嚴格有序。
每個fields包括,“name”、“doc”、“type”、“default”、
“order”、“aliases”幾個屬性。其中在fields列表中每個filed應該擁有不重複的name,“type”表示field的數據類型。 default”很明顯,用來表示field的默認值,當reader讀取時,當沒有此field時將會採用默認值;“order”:可選值,排序(ascending、descending、ignore),在Mapreduce集成時有用。 “aliases”別名,JSON Array,表示此field的別名列表。
enum:枚舉類型。“symbols”屬性即位enum的常量值列表。值不能重複。
fixed:表示field的值的長度爲“固定值”,“size”屬性表示值的字節長度。
union:集合,數學意義上的“集合”,集合中的數據不能重複。如果對“type”使用union類型,那麼其default值必須和union的第一個類型匹配。

Avro 的java實例

首先我們需要在pom.xml文件中增加如下配置:

1. <dependency>  
2.     <groupId>org.apache.avro</groupId>  
3.     <artifactId>avro</artifactId>  
4.     <version>1.7.7</version>  
5. </dependency>  
6.   
7. <dependency>  
8.     <groupId>org.apache.avro</groupId>  
9.     <artifactId>avro-tools</artifactId>  
10.     <version>1.7.7</version>  
11. </dependency>  

avro和avro-tools兩個依賴包,是avro開發的必備的基礎包。如果需要讓maven來根據.avsc文件生成java代碼的話,還需要增加如下avro-maven-plugin依賴,否則此處是不需要的。

1. <plugin>  
2.     <groupId>org.apache.avro</groupId>  
3.     <artifactId>avro-maven-plugin</artifactId>  
4.     <version>1.7.7</version>  
5.     <executions>  
6.         <execution>  
7.             <phase>generate-sources</phase>  
8.             <goals>  
9.                 <goal>schema</goal>  
10.             </goals>  
11.             <configuration>  
12.                 <sourceDirectory>${project.basedir}/src/main/resources/avro/</sourceDirectory>  
13.                 <outputDirectory>${project.basedir}/src/main/java/</outputDirectory>  
14.             </configuration>  
15.         </execution>  
16.     </executions>  
17. </plugin>  
18. <plugin>  
19.     <groupId>org.apache.maven.plugins</groupId>  
20.     <artifactId>maven-compiler-plugin</artifactId>  
21.     <configuration>  
22.         <encoding>utf-8</encoding>  
23.     </configuration>  
24. </plugin>  

不基於代碼生成的方式

定義模式Pair.avsc(.avsc是avro Schema文件的常用擴展名)

1. {
2.  "namespace": "test.avro",   
3.  "type": "record",   
4.  "name": "Pair",  
5.  "doc":"A Pair of Strings.",
6.  "fields": [  
7.      {"name": "left", "type": "string"},  
8.      {"name": "right", "type":"string" },  
9.  ]  
10. }  

namespace類似於包名,name類似於類名,namespace加上name是這個類的全名,fields內的內容相當於類的變量。
可以通過聲明來加載這個模式:
Schema schema =Schema.parse(file);;
file爲.avsc文件,然後可以使用下述的API來創建Avro記錄的實例:

GenericRecord datum =new GenericData.Record(schema);
datum.put("left",new Utf8("L"));
datum.put("right",new Utf8("R"));

接下來就可以執行序列化過程:

DatumWriter<GenericRecord> userDatumWriter =new SpecificDatumWriter<GenericRecord>(schema);
DataFileWriter<GenericRecord> dataFileWriter = new DataFileWriter<GenericRecord>(userDatumWriter);
dataFileWriter.create(schema, storageFile);
dataFileWriter.append(datum);
dataFileWriter.close();

下面的代碼反序列化:

DatumReader<GenericRecord> datumReader =new GenericDatumReader<GenericRecord>(schema);
DataFileReader<GenericRecord> dataFileReader = new DataFileReader<GenericRecord>(storageFile,datumReader);
GenericRecord record =null;
while(dataFileReader.hasNext()){
record=dataFileReader.next();
System.out.println(record);
}

這種情況下,沒有生成JAVA API,那麼序列化過程就需要開發者預先熟悉Schema的結構,創建User的過程就像構建JSON字符串一樣,通過put操作來“賦值”。反序列化也是一樣,需要指定schema。
GenericRecord接口提供了根據“field”名稱獲取值的方法:Object get(String fieldName);不過需要聲明,這內部實現並不是基於map,而是一個數組,數組和Schema聲明的Fileds按照index對應。put操作根據field名稱找到對應的index,然後賦值;get反之。那麼在對待Schema兼容性上和“代碼生成”基本一致。

基於代碼生成的方式

定義schema,user.avsc
{"namespace":"test.avro",
"type":"record",
"name":"User",
"fields":[
{"name":"name","type":"string"},
{"name":"favourite_number","type":["int","null"]},
{"name":"favourite_color","type":["string","null"]}
]
}

“根據Avro schema生成JAVA代碼,然後根據JAVA API來進行數據操作。如果你在pom.xml配置了avro-maven-plugin插件,那麼只需要只需要執行:
maven compile
此後插件將會根據user.avsc文件在project指定的package目錄下生成java代碼。此外如果你的項目不是基於maven的,或者你不希望maven來做這件事,也可以使用如下命令生成java代碼,此後只需要將代碼copy到項目中即可:

1. java -jar /path/to/avro-tools-1.7.7.jar compile schema <schema file> <code destination> 
2. java -jar avro-tools-1.7.7.jar compile schema user.avsc ./  

序列化、發序列化Java代碼樣例:
//Three ways to create user
File file =new File("e:\\user.avro");
User user =new User();
user.setName("zhang");
user.setFavouriteColor("blue");
user.setFavouriteNumber(12);
User user1 =new User("shan",8,"blue");
User user2 = User.newBuilder()
.setName("lucker")
.setFavouriteColor("blcak")
.setFavouriteNumber(38)
.build();
//serialization
try {
DatumWriter<User> userDatumWriter =new SpecificDatumWriter<User>(User.class);
DataFileWriter<User> dataFileWriter = new DataFileWriter<User>(userDatumWriter);
dataFileWriter.create(user.getSchema(),file);
dataFileWriter.append(user);
dataFileWriter.append(user);
dataFileWriter.append(user1);
dataFileWriter.append(user2);
dataFileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
//deserialization
try {
DatumReader<User> userDatumReader =new SpecificDatumReader<User>(User.class);
DataFileReader<User> dataFileReader =new DataFileReader<User>(file,userDatumReader);
User u =null;
while(dataFileReader.hasNext()){
u=dataFileReader.next();
System.out.println(u);
}
} catch (IOException e) {
e.printStackTrace();
}

代碼很簡單,描述了將User通過avro schema寫入文件,我們可以通過二進制文本編輯器查看這個結果文件,會發現文件的開頭部分是schema,後續是逐個user序列化的二進制結果。代碼生成時,User的Schema信息已經作爲一個靜態常量寫入了User.java中,同時根據schema中fields的列表嚴格順序,顯式的生成Fields數組,數組的index爲schema中filed的聲明的位置。

Schema解析

Schema通過JSON對象表示。Schema定義了簡單數據類型和複雜數據類型,其中複雜數據類型包含不同屬性。通過各種數據類型用戶可以自定義豐富的數據結構。
Schema有下列JSON對象之一定義:
1. JSON字符串:命名
2. JSON對象:{“type”: “typeName” …attributes…}
3. JSON數組:Avro中Union的定義
可以選擇一個與寫入模式不同的讀取模式來讀取數據,這種方式成爲模式演化。
考慮模式Pair,如果對其添加description使其成爲一個新模式newSchema:

1. {
2.  "namespace": "test.avro",   
3.  "type": "record",   
4.  "name": "Pair",  
5.  "doc":"A Pair of Strings.",
6.  "fields": [  
7.      {"name": "left", "type": "string"},  
8.      {"name": "right", "type":"string" }, 
9.      {"name":"description","type":"string","defualt":""}
10.  ]  
11. }  

使用這個新的模式讀取前面序列化的數據,以爲description指定了默認值,所以Avro在讀取沒有定義該字段的記錄時就會使用這個空值。注意:如果希望將默認值設置爲null,則需要使用avro的並集指定description的type:

1. {"name":"description","type":"string","type":["null","string"],"defualt":"null"}

讀取的代碼如下,讀取兩個模式對象,讀取對象和寫入對象:

1. DatumReader<GenericRecord> reader =new GenericDatumReader<GenericRecord>(schema,newSchema);
2. Decoder decoder=DecoderFactory.defaultFactory().createBinaryDecoder(out.toByteArray(),null);
3. GenericRecord result =reader.read(null,decoder);
4. assertThat(result.get("left").toString(),is("L"));
5. assertThat(result.get("right").toString(),is("R"));
6. assertThat(result.get("description").toString(),is(""));

不同讀取模式的另一個應用是去掉記錄中的某些字段,該操作可以稱爲“投影”;

1. {
2.  "namespace": "test.avro",   
3.  "type": "record",   
4.  "name": "Pair",  
5.  "doc":"A Pair of Strings.",
6.  "fields": [  
7.      {"name": "left", "type": "string"},  
8.  ]  
9. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章