您的位置:首页 > 其它

Avro简介

2015-08-19 11:11 477 查看

概述

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. }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: