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”
int32位带符号整数“int”
long64位带符号整数“long”
float单精度(32位)IEEE754浮点数“float”
double双精度(64位)IEEE754浮点数 “double”
bytes8位无符号字节序列“bytes”
stringUnicode字符序列“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. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值