最近项目可能要用到Avro,在网上找了不少例子,现在把学习内容坐下总结,方便接下来使用。Apache Avro 是Hadoop下的一个子项目,它本身既是一个序列化框架,同时也实现了RPC的功能。
编写一个AVro应用通常需要如下三步:
1、定义消息格式文件,通常以avro作为扩展名
2、使用avro编译器生成特定语言的代码文件(可选)
3、使用avro库提供的API来编写应用程序
参照官网http://avro.apache.org/docs/current/gettingstartedjava.html有java快速使用说明
使用到的jar包有:avro-1.7.7.jar、avro-tools-1.7.7.jar、 jackson-core-asl-1.8.8.jar、jackson-mapper-asl-1.8.8.jar,这些直接网上百度下也行
1、编写的user.avro文件内容如下
{
"namespace": "example.avro", //namespace表示生成的文件存放在哪个文件夹下
"type": "record",
"name": "User",
"fields": [
{
"name": "name",
"type": "string"
},
{
"name": "favorite_number",
"type": [
"int",
"null"
]
},
{
"name": "favorite_color",
"type": [
"string",
"null"
]
}
]
}
保存在d盘根目录下。
2、使用avro-tools生成java代码
在命令行进入到d盘输入
java -jar avro-tools-1.7.7.jarcompile schema user.avro.
命令行最后有个小点,注意可以换成你想保存的目的地址。将在该目录下生成./example/avro/User.java 文件
3、序列化和反序列化文件,注意将上面提到的4个jar包导入你的程序。这里直接是在Eclipse中构建程序,看了下往上多数是先用maven再生成了Eclipse,好吧maven我不熟。再把生成的user.avro文件考到项目中来。
import java.io.File;
import java.io.IOException;
import org.apache.avro.file.DataFileReader;
import org.apache.avro.file.DataFileWriter;
import org.apache.avro.io.DatumReader;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.specific.SpecificDatumReader;
import org.apache.avro.specific.SpecificDatumWriter;
public class Test {
public static void main(String[] args) throws IOException {
code();
decode();
}
public static void code() throws IOException{
User user1 = new User();
user1.setName("Alyssa");
user1.setFavoriteNumber(256);
// Leave favorite color null
// Alternate constructor
User user2 = new User("Ben", 7, "red");
// Construct via builder
User user3 = User.newBuilder().setName("Charlie").setFavoriteColor("blue")
.setFavoriteNumber(null).build();
User user4 = new User("Jimmy", 7, "yellow");
// Serialize user1 and user2 to disk
File file = new File("users.avro");
DatumWriter<User> userDatumWriter = new SpecificDatumWriter<User>(
User.class);
DataFileWriter<User> dataFileWriter = new DataFileWriter<User>(
userDatumWriter);
dataFileWriter.create(user1.getSchema(), new File("users.avro"));
dataFileWriter.append(user1);
dataFileWriter.append(user2);
dataFileWriter.append(user3);
dataFileWriter.append(user4);
dataFileWriter.close();
}
public static void decode() throws IOException{
// Deserialize Users from disk
DatumReader<User> userDatumReader = new SpecificDatumReader<User>(User.class);
File file = new File("users.avro");
DataFileReader<User> dataFileReader = new DataFileReader<User>(file , userDatumReader);
User user = null;
while (dataFileReader.hasNext()) {
// Reuse user object by passing it to next(). This saves us from
// allocating and garbage collecting many objects for files with
// many items.
user = dataFileReader.next(user);
System.out.println(user);
}
}
}
运行得到的结果如下:
{"name": "Alyssa", "favorite_number": 256, "favorite_color": null}
{"name": "Ben", "favorite_number": 7, "favorite_color": "red"}
{"name": "Charlie", "favorite_number": null, "favorite_color": "blue"}
{"name": "Jimmy", "favorite_number": 7, "favorite_color": "yellow"}
但是没有自动在Eclipse工程根目录下生成,估计是哪出了问题。以上参考:http://www.bianceng.cn/Servers/web/201411/46469.htm
下面介绍不生成user.java代码的方法:
无需生成对应代码的序列化与反序列化
在avro中的数据总是与相应的schema存储在一起的,这意味着无论我们提前知道schema与否,总是能够读取一段序列化的项。这就使得我们可以在没有代码生成这一步时完成序列化与反序列化。
让我们重复之前的例子,但是不适用代码生成。我们将新建一些users,并将它们序列化存储到硬盘数据文件,然后读回文件并且反序列化成用户对象。
创建用户
首先,适用Parser来读取schema定义并且创建一个模式对象。
Schema schema = new Schema.Parser().parse(new File("user.avsc"));
应用此schema来创建一些users
GenericRecord user1 = new GenericData.Record(schema);
user1.put("name", "Alyssa");
user1.put("favorite_number", 256);
// Leave favorite color null
GenericRecord user2 = new GenericData.Record(schema);
user2.put("name", "Ben");
user2.put("favorite_number", 7);
user2.put("favorite_color", "red");
由于没有使用代码生成,这里使用GenericRecord来代表users。GenericRecord使用schema来验证我们指定的有效字段(field)例如(e.g., user1.put("favorite_animal", "cat"),这时程序会产生AvroRuntimeException异常。
注意这里没有设置user1的favorite color,因为这种类型的记录是(“string”,"null"),所以可以把它设置为string 或者令其为null,本质上它是可选的。
序列化
接下来创建user对象,对他们进行序列化和反序列化的工作基本上是和前面使用代码生成的例子相同。主要区别在于这里使用泛型而不是指定的readers and writers
把users序列化到硬盘文件:
/ /Serialize user1 and user2 to disk
File file = new File("users.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();
这里创建了一个DatumWriter来把java对象转化成为内存中的序列格式。由于没有使用代码生成,需要创建一个GenericDatumWriter。它需要通过schema来确定如何编写GenericRecords以及验证所有的费用字段(field)存在。
正如在代码生成的例子中我们创建了一个DataFieldWriter来write下序列化的记录以及schema,并且保存到dataFileWriter.creat命令中指定的文件。这里使用dataFileWriter.append方法来把users写入到文件去,写完后需要关闭文件。
反序列化:
最后,把刚才创建的序列化文件进行反序列化:
// Deserialize users from disk
DatumReader<GenericRecord> datumReader = new GenericDatumReader<GenericRecord>(schema);
DataFileReader<GenericRecord> dataFileReader = new DataFileReader<GenericRecord>(file, datumReader);
GenericRecord user = null;
while (dataFileReader.hasNext()) {
// Reuse user object by passing it to next(). This saves us from
// allocating and garbage collecting many objects for files with
// many items.
user = dataFileReader.next(user);
System.out.println(user);
输出结果为:
{"name": "Alyssa", "favorite_number": 256, "favorite_color": null}
{"name": "Ben", "favorite_number": 7, "favorite_color": "red"}
反序列化与序列化过程很相似。创建一个GenericDatumReader,把内存中序列化的项转换成GenericRecords.将DatumReader和之前创建的序列化文件传递给DataFileReader,读取硬盘上的数据文件。
然后使用DataFileReader来迭代序列化过的users,并且将反序列化的结果进行输出。这里有一个最佳实践:即允许DataFileReader重复使用相同的record对象而不是给每次迭代过程分配一个新的GenericRecord(当反序列化一个很大的文件时,它很耗费对象分配地址和垃圾回收),如果在性能上没有要求可以使用GenericRecord user : dataFileReader
最后运行样例代码:
本示例代码作为一个maven项目包含存放在example/java-exanple中,从该文件目录下,执行如下命令:
$ mvn compile
$ mvn -q exec:java -Dexec.mainClass=example.GenericMain