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” |
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. }
相关文章推荐
- 根据进程ID杀死进程
- 从"c\windows\dms.exe"变为"c\windows\"
- UVA 10594-Date Flow(无向图的最小费用网络流+题目给的数据有误)
- 关于cookie的相关操作
- Photoshop将鞋子打造出打散的发光小碎片
- linux下检查内存泄露的工具--mtrace
- 【OC 第3课】NSString ,NSMutableString用法以及一些常用方法
- HTML5+ 打开关闭侧滑窗口
- Loadrunner参数表中select_next_row和update_value_on的不同组合设置对参数取值的影响
- 如何搞定foxmail下的.eml文件导入到win7内的outlook2007
- c实现极简单的正则表达式解析
- 对于STM32别名区的理解 (转载)
- [转]NAT、防火墙的原理区别和分类
- 根据进程ID获取exe路径
- 火云开发课堂 - 《使用Cocos2d-x 开发3D游戏》系列 第四节:3D公告板
- 【动态语言和静态语言】动态语言和静态语言的认识,定义
- iOS开发~CocoaPods使用详细说明
- php 在windows下配置虚拟目录的方法
- B-树
- 我对嵌入式软件的理解