1. 简介
Apache Avro(以下简称 Avro)是一种与编程语言无关的序列化格式。Doug Cutting 创建了这个项目,目的是提供一种共享数据文件的方式。
Avro 数据通过与语言无关的 schema 来定义。schema 通过 JSON 来描述,数据被序列化成二进制文件或 JSON 文件,不过一般会使用二进制文件。Avro 在读写文件时需要用到 schema,schema 一般会被内嵌在数据文件里。
Avro 有一个很有意思的特性是,当负责写消息的应用程序使用了新的 schema,负责读消息的应用程序可以继续处理消息而无需做任何改动。
到写本篇博客的时间为止,avro的最新版本为1.8.2
2. 创建 maven 工程
(1) 加入 avro 依赖
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>1.8.2</version>
</dependency>
(2) 加入 avro 插件的依赖
<plugin>
<groupId>org.apache.avro</groupId>
<artifactId>avro-maven-plugin</artifactId>
<version>1.8.2</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>schema</goal>
</goals>
<configuration>
<sourceDirectory>${project.basedir}/src/main/avro/</sourceDirectory>
<outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
以上是官网列出的 avro 插件的依赖,其中提供了 maven 的编译插件,该插件使用JDK1.6版本来编译代码,我在这里改为了1.8,因为我的JDK版本是1.8
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
在引入这个插件后,在 pom.xml 中会有编译错误(错误原因我也不清楚),选择忽略即可
选择忽略之后,在 pom 中会自动生成以下配置来说明 pom 文件已经忽略了 avro 插件引起的错误
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.avro</groupId>
<artifactId>avro-maven-plugin</artifactId>
<versionRange>[1.8.2,)</versionRange>
<goals>
<goal>schema</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
以上错误是在 Eclipse 中创建 avro 的 maven 项目时才会出现,在 IDEA 中就不会出现这种情况。
(3) 更新 maven 工程
作了以上修改后,发现 maven 项目上有报错,但 pom 中并没有错误:
在项目上右键更新maven项目即可:
3. 使用 avro
(1) 通过生成代码的方式使用 avro
<1> 定义 schema 文件
注意在 avro 插件的依赖中定义的两个路径
<configuration>
<sourceDirectory>${project.basedir}/src/main/avro/</sourceDirectory>
<outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
</configuration>
该配置的意思是,根据/src/main/avro/下的schema文件,生成对应的类文件到/src/main/java/下,所以我们先创建一个资源文件夹/src/main/avro
然后再在该资源文件夹下创建 schema 文件,这里定义一个简单的schema文件user.avsc
,注意,后缀一定是avsc
,其中的内容如下:
{
"namespace": "com.avro.example",
"type": "record",
"name": "User",
"fields": [
{"name": "name", "type": "string"},
{"name": "favorite_number", "type": ["int", "null"]},
{"name": "favorite_color", "type": ["string", "null"]}
]
}
- namespace:定义了根据 schema 文件生成的类的包名
- type:固定写法
- name:生成的类的名称
- fields:定义了生成的类中的属性的名称和类型,其中
"type": ["int", "null"]
的意思是,favorite_number 这个属性是int类型,但可以为null
avro 支持的类型有null、boolean、int、long、float、double、bytes、string这些基本类型和record、enum、array、map、union、fixed这些复杂类型,关于复杂类型可以参考官网的说明:http://avro.apache.org/docs/current/spec.html#schema_complex,本文只是一个入门
<2> 生成 User 类
在编译程序之前,项目中是没有com.avro.example.User这个类的:
在运行 maven build compile 后,就生成这个类:
<3> 序列化
package com.avro.serializer;
import java.io.File;
import java.io.IOException;
import org.apache.avro.file.DataFileWriter;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.specific.SpecificDatumWriter;
import com.avro.example.User;
/**
* @Title AvroSerializerTest.java
* @Description 使用 avro 对 com.avro.example.User 类的对象进行序列化
* @Author YangYunhe
* @Date 2018-06-21 15:42:02
*/
public class AvroSerializerTest {
public static void main(String[] args) throws IOException {
User user1 = new User();
user1.setName("Tom");
user1.setFavoriteNumber(7);
User user2 = new User("Jack", 15, "red");
User user3 = User.newBuilder()
.setName("Harry")
.setFavoriteNumber(1)
.setFavoriteColor("green")
.build();
DatumWriter<User> userDatumWriter = new SpecificDatumWriter<>(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.close();
}
}
运行以上程序,就会把这3个User对象经过 avro 序列化后写到了项目根目录下的"user.avro"文件中:
<4> 反序列化
package com.avro.deserializer;
import java.io.File;
import java.io.IOException;
import org.apache.avro.file.DataFileReader;
import org.apache.avro.io.DatumReader;
import org.apache.avro.specific.SpecificDatumReader;
import com.avro.example.User;
/**
* @Title AvroDeSerializerTest.java
* @Description 解析 avro 序列化后的对象
* @Author YangYunhe
* @Date 2018-06-21 15:58:10
*/
public class AvroDeSerializerTest {
public static void main(String[] args) throws IOException {
DatumReader<User> userDatumReader = new SpecificDatumReader<User>(User.class);
DataFileReader<User> dataFileReader = new DataFileReader<User>(new File("users.avro"), userDatumReader);
User user = null;
while (dataFileReader.hasNext()) {
user = dataFileReader.next(user);
System.out.println(user);
}
}
}
程序运行结果为:
{"name": "Tom", "favorite_number": 7, "favorite_color": null}
{"name": "Jack", "favorite_number": 15, "favorite_color": "red"}
{"name": "Harry", "favorite_number": 1, "favorite_color": "green"}
(2) 通过不生成代码的方式使用 avro
<1> 序列化
package com.avro.serializer;
import java.io.File;
import java.io.IOException;
import org.apache.avro.Schema;
import org.apache.avro.file.DataFileWriter;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.DatumWriter;
import com.avro.deserializer.AvroDeSerializerWithoutCodeGenerationTest;
/**
* @Title AvroSerializerWithoutCodeGenerationTest.java
* @Description 通过不生成代码的方式使用avro序列化User对象
* @Author YangYunhe
* @Date 2018-06-21 16:04:13
*/
public class AvroSerializerWithoutCodeGenerationTest {
public static void main(String[] args) throws IOException {
String avscFilePath =
AvroDeSerializerWithoutCodeGenerationTest.class.getClassLoader().getResource("user.avsc").getPath();
Schema schema = new Schema.Parser().parse(new File(avscFilePath));
GenericRecord user1 = new GenericData.Record(schema);
user1.put("name", "Tony");
user1.put("favorite_number", 18);
GenericRecord user2 = new GenericData.Record(schema);
user2.put("name", "Ben");
user2.put("favorite_number", 3);
user2.put("favorite_color", "red");
File file = new File("user2.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();
}
}
<2> 反序列化
package com.avro.deserializer;
import java.io.File;
import java.io.IOException;
import org.apache.avro.Schema;
import org.apache.avro.file.DataFileReader;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.DatumReader;
/**
* @Title AvroDeSerializerWithoutCodeGenerationTest.java
* @Description 通过不生成代码的方式使用avro反序列化
* @Author YangYunhe
* @Date 2018-06-21 16:07:44
*/
public class AvroDeSerializerWithoutCodeGenerationTest {
public static void main(String[] args) throws IOException {
String avscFilePath =
AvroDeSerializerWithoutCodeGenerationTest.class.getClassLoader().getResource("user.avsc").getPath();
Schema schema = new Schema.Parser().parse(new File(avscFilePath));
File file = new File("user2.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": "Tony", "favorite_number": 18, "favorite_color": null}
{"name": "Ben", "favorite_number": 3, "favorite_color": "red"}