Avro简介及Java运用

简介

Avro是一种远程过程调用和数据序列化框架,是在Apache的Hadoop项目之内开发的。它使用JSON来定义数据类型和通讯协议,使用压缩二进制格式来序列化数据。它主要用于Hadoop,它可以为持久化数据提供一种序列化格式,Avro是一个数据序列化的系统。可以将数据结构或对象转化成便于存储或传输的格式。Avro设计之初就用来支持数据密集型应用,适合于远程或本地大规模数据的存储和交换。

序列化及反序列化概念

  • 把对象转换为字节序列的过程称为对象的序列化
  • 把字节序列恢复为对象的过程称为对象的反序列化

对象的序列化主要有两种用途:

  1. 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中
  2. 在网络上传送对象的字节序列。

  在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

  当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

Avro特点

  • 丰富的数据结构类型;

  • 快速可压缩的二进制数据形式,对数据二进制序列化后可以节约数据存储空间和网络传输带宽;

  • 存储持久数据的文件容器;

  • 可以实现远程过程调用RPC;

  • 简单的动态语言结合功能。

数据类型

基础数据类型

Type Description Schema
null the absence of a value “null”
boolean a binary value “boolean”
int 32-bit signed integer “int”
long 64-bit signed integer “long”
float Single-precision (32-bit) IEEE 754 floating-point number “float”
double Double-precision (64-bit) IEEE 754 floating-point number “double”
bytes Sequence of 8-bit unsigned bytes “bytes”
string Sequence of Unicode characters “string”

复杂数据类型

Avro提供了6种复杂类型。分别是Record,Enum,Array,Map,Union和Fixed。

Record

Record是一个任意类型的命名字段的集合

支持的属性设置:

  • name:record类型的名字(必填)
  • namespace:命名空间(可选)
  • doc:这个类型的文档说明(可选)
  • aliases:record类型的别名,是个字符串数组(可选)
  • fields:record类型中的字段,是个对象数组(必填)。每个字段需要以下属性:

    1. name:字段名字(必填)
    2. doc:字段说明文档(可选)
    3. type:一个schema的json对象或者一个类型名字(必填)
    4. default:默认值(可选)
    5. order:排序(可选),只有3个值ascending(默认),descending或ignore
    6. aliases:别名,字符串数组(可选)

一个Record类型例子,定义一个元素类型是Long的链表:

1
2
3
4
5
6
7
8
9
{
"type": "record",
"name": "LongList",
"aliases": ["LinkedLongs"], // old name for this
"fields" : [
{"name": "value", "type": "long"}, // each element has a long
{"name": "next", "type": ["null", "LongList"]} // optional next element
]
}

Enum

枚举类型的类型名字是”enum”,还支持其它属性的设置:

  • name:枚举类型的名字(必填)
  • namespace:命名空间(可选)
  • aliases:字符串数组,别名(可选)
  • doc:说明文档(可选)
  • symbols:字符串数组,所有的枚举值(必填),不允许重复数据。

一个枚举类型的例子:

1
2
3
4
{ "type": "enum",
"name": "Suit",
"symbols" : ["SPADES", "HEARTS", "DIAMONDS", "CLUBS"]
}

Array

数组类型的类型名字是”array”并且只支持一个属性:

items:数组元素的schema

一个数组例子:

1
2
3
4
{
"type": "array",
"items": "string"
}

Map

Map类型的类型名字是”map”并且只支持一个属性:

values:map值的schema

Map的key必须是字符串。

一个Map例子:

1
2
3
4
{
"type": "map",
"values": "long"
}

Union

组合类型,表示各种类型的组合,使用数组进行组合。比如[“null”, “string”]表示类型可以为null或者string。

如以下例子

1
2
3
4
5
6
7
8
9
10
{
"type": "record",
"namespace": "com.example",
"name": "FullName",
"fields":
[
{ "name": "first", "type": ["null", "string"] },
{ "name": "last", "type": "string", "default" : "Doe" }
]
}

组合类型的默认值是看组合类型的第一个元素,因此如果一个组合类型包括null类型,那么null类型一般都会放在第一个位置,这样子的话这个组合类型的默认值就是null。

组合类型中不允许同一种类型的元素的个数不会超过1个,除了record,fixed和enum。比如组合类中有2个array类型或者2个map类型,这是不允许的。

组合类型不允许嵌套组合类型。

Fixed

混合类型的类型名字是fixed,支持以下属性:

  • name:名字(必填)
  • namespace:命名空间(可选)
  • aliases:字符串数组,别名(可选)
  • size:一个整数,表示每个值的字节数(必填)

比如16个字节数的fixed类型例子如下:

1
2
3
4
5
{
"type": "fixed",
"size": 16,
"name": "md5"
}

java实践

配置

引入pom.xml

1
2
3
4
5
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>1.8.2</version>
</dependency>

下载avro-tool

avro-tools-1.8.2.jar 该jar包主要用户将定义好的schema文件生成对应的java文件
下载地址

schema

Avro依赖模式(Schema)来实现数据结构定义。可以把模式理解为Java的类,它定义每个实例的结构,可以包含哪些属性。可以根据类来产生任意多个实例对象。对实例序列化操作时必须需要知道它的基本结构,也就需要参考类的信息。这里,根据模式产生的Avro对象类似于类的实例对象。每次序列化/反序列化时都需要知道模式的具体结构。所以,在Avro可用的一些场景下,如文件存储或是网络通信,都需要模式与数据同时存在。Avro数据以模式来读和写(文件或是网络),并且写入的数据都不需要加入其它标识,这样序列化时速度快且结果内容少。由于程序可以直接根据模式来处理数据,所以Avro更适合于脚本语言的发挥。

定义schema

avro的schema是json格式,支持前文所描述的类型。
下面是一个schema例子 User.avsc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"type": "record",
"name": "User",
"fields": [
{
"name": "name",
"type": "string",
"doc":"姓名"
},
{
"name": "favorite_number",
"type": [
"int",
"null"
]
},
{
"name": "favorite_color",
"type": [
"string",
"null"
]
}
]
}

编译schema

使用avro-tools-1.8.2.jar 编译
编译命令格式如下:

java -jar avro-tools-1.8.2.jar compile schema <filename.avsc> <outputpath>

序列化与反序列化

avro的序列化和反序列化,有两种方法。第一种是需要利用schema生成实体类,另外一种只需要指定schema并使用GenericRecord作为实体。

生成实体类User

编译schema至java项目src目录

序列化

AvroSerialize.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.io.*;

import org.apache.avro.file.*;
import org.apache.avro.io.*;
import org.apache.avro.specific.*;


public class AvroSerialize {
public static void main(String args[]) throws IOException {

// 新建3个实体类
User user1 = new User();
user1.setName("Alyssa");
user1.setFavoriteNumber(256);

User user2 = new User("Ben", 7, "red");

User user3 = User.newBuilder()
.setName("Charlie")
.setFavoriteColor("blue")
.setFavoriteNumber(null)
.build();

DatumWriter<User> userDatumWriter = new SpecificDatumWriter<User>(User.class);
DataFileWriter<User> dataFileWriter = new DataFileWriter<User>(userDatumWriter);
dataFileWriter.create(user1.getSchema(), new File("./src/main/avro/user.avro"));
dataFileWriter.append(user1);
dataFileWriter.append(user2);
dataFileWriter.append(user3);
dataFileWriter.close();
}
}

反序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;

import org.apache.avro.file.*;
import org.apache.avro.io.*;
import org.apache.avro.specific.*;
public class AvroDeserialize {
public static void main(String args[]) {
DatumReader<User> reader = new SpecificDatumReader<>(User.class);
DataFileReader<User> fileReader = null;
try {
fileReader = new DataFileReader<User>(new File("./src/main/avro/user.avro"), reader);
User user = null;
while (fileReader.hasNext()) {
user = fileReader.next(user);
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

输出

{“name”: “Alyssa”, “favorite_number”: 256, “favorite_color”: null}
{“name”: “Ben”, “favorite_number”: 7, “favorite_color”: “red”}
{“name”: “Charlie”, “favorite_number”: null, “favorite_color”: “blue”}

使用GenericRecord

序列化

AvroGenericDeserializa.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package avro;
import java.io.*;

import org.apache.avro.Schema;
import org.apache.avro.file.*;
import org.apache.avro.generic.*;
import org.apache.avro.io.*;

public class AvroGenericDeserializa {
public static void main(String args[]) throws IOException {
Schema schema = new Schema.Parser().parse(new File("./src/main/avro/User.avsc"));
GenericRecord user1 = new GenericData.Record(schema);
user1.put("name", "Alyssa");
user1.put("favorite_number", 256);

GenericRecord user2 = new GenericData.Record(schema);
user2.put("name", "Ben");
user2.put("favorite_number", 7);
user2.put("favorite_color", "red");

File file = new File("./src/main/avro/user_generic.avro");
DatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<GenericRecord>(schema);
DataFileWriter<GenericRecord> dataFileWriter = new DataFileWriter<GenericRecord>(datumWriter);
dataFileWriter.create(schema, file);
dataFileWriter.append(user1);
dataFileWriter.append(user2);
dataFileWriter.close();
}
}

反序列化

AvroGenericSerialize.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.*;

import org.apache.avro.Schema;
import org.apache.avro.file.*;
import org.apache.avro.generic.*;
import org.apache.avro.io.*;

public class AvroGenericSerialize {
public static void main(String args[]) throws IOException {
Schema schema = new Schema.Parser().parse(new File("./src/main/avro/User.avsc"));
File file = new File("./src/main/avro/user_generic.avro");
DatumReader<GenericRecord> datumReader = new GenericDatumReader<GenericRecord>(schema);
DataFileReader<GenericRecord> dataFileReader = new DataFileReader<GenericRecord>(file, datumReader);
GenericRecord user = null;
while (dataFileReader.hasNext()) {
user = dataFileReader.next(user);
System.out.println(user);
}
}
}

输出

{“name”: “Alyssa”, “favorite_number”: 256, “favorite_color”: null}
{“name”: “Ben”, “favorite_number”: 7, “favorite_color”: “red”}

Have a nice day!