【Apache NIFI 翻译】Apache Avro™ 1.10.0 Getting Started (Java)
这是使用Java入门Apache Avro™的简短指南。本指南仅涵盖使用Avro进行数据序列化。请参阅Patrick Hunt的 Avro RPC Quick Start,以获取有关将Avro用于RPC的良好介绍。
Download
可以从 Apache Avro™ Releases页面下载C,C ++,C#,Java,PHP,Python和Ruby的Avro实现。本指南使用撰写时的最新版本Avro 1.10.0。有关本指南中的示例,请下载avro-1.10.0.jar和avro-tools-1.10.0.jar。
或者,如果您使用的是Maven,则将以下依赖项添加到POM中:
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>1.10.0</version>
</dependency>
以及Avro Maven插件(用于执行代码生成):
<plugin>
<groupId>org.apache.avro</groupId>
<artifactId>avro-maven-plugin</artifactId>
<version>1.10.0</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.8</source>
<target>1.8</target>
</configuration>
</plugin>
您也可以从源代码构建所需的Avro jar。构建Avro超出了本指南的范围。有关更多信息,请参见Wiki中的 Build Documentation页面。
Defining a schema
Avro schemas是使用JSON定义的。schemas由基本类型(null, boolean, int, long, float, double, bytes, and string)和复杂类型(record, enum, array, map, union, and fixed)组成。您可以从specification中了解有关Avro schema和类型的更多信息,但是现在让我们从一个简单的schema示例user.avsc开始:
{"namespace": "example.avro",
"type": "record",
"name": "User",
"fields": [
{"name": "name", "type": "string"},
{"name": "favorite_number", "type": ["int", "null"]},
{"name": "favorite_color", "type": ["string", "null"]}
]
}
该schema定义代表假设用户的record。 (请注意,一个schema文件只能包含一个schema定义。)record定义至少必须包括其类型(“ type”:“ record”),名称(“ name”:“ User”)和字段。我们还定义了一个名称空间(“ namespace”:“ example.avro”),该名称空间与name属性一起定义了schema的“全名”(在这种情况下为example.avro.User)。
fields是通过对象数组定义的,每个对象都定义了名称和类型(其他属性是可选的,有关更多详细信息,请参见record specification)。field的type属性是另一个schema对象,可以是基本类型或复杂类型。例如,我们的User schema的name字段是基本类型string,而favourite_number和favourite_color字段都是union,由JSON数组表示。union是一个复杂类型,可以是数组中列出的任何类型;例如,favorite_number可以是int或null,本质上使其成为可选字段。
Serializing and deserializing with code generation
通过代码生成进行序列化和反序列化。
Compiling the schema 编译schema
代码生成使我们能够基于先前定义的shema自动创建class。一旦定义了相关的类,就无需在程序中直接使用schema。我们使用avro-tools jar生成代码,如下所示:
java -jar /path/to/avro-tools-1.10.0.jar compile schema <schema file> <destination>
这将根据提供的目标文件夹中schema的名称空间在包中生成适当的源文件。例如,要从上面定义的schema在包example.avro中生成User类,请运行:
java -jar /path/to/avro-tools-1.10.0.jar compile schema user.avsc .
请注意,如果您使用Avro Maven插件,则无需手动调用schema编译器。该插件会自动对配置的源目录中存在的任何.avsc文件执行代码生成。
Creating Users 创建Users
现在我们已经完成了代码生成,让我们创建一些Users,将它们序列化为磁盘上的数据文件,然后读回该文件并反序列化User对象。 首先,让我们创建一些Users并设置其字段。
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();
如本示例所示,可以通过直接调用constructor函数或使用builder函数来创建Avro对象。与 constructor函数不同,builder函数将自动设置schema中指定的任何默认值。此外,builder会按设置的方式验证数据,而直接construct的对象在序列化对象之前不会导致错误。但是,直接使用 constructor函数通常可以提供更好的性能,因为builder函数会在写入数据结构之前创建一个副本。
Serializing
现在让我们将Users序列化到磁盘上。
// Serialize user1, user2 and user3 to disk
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.close();
我们创建了一个DatumWriter,它将Java对象转换为内存中的序列化格式。 SpecificDatumWriter类与生成的类一起使用,并从指定的生成类型中提取shema。 接下来,我们创建一个DataFileWriter,它将序列化的记录以及shema写入dataFileWriter.create调用中指定的文件中。我们通过调用dataFileWriter.append方法将Users写入文件。完成写入后,我们将关闭数据文件。
Deserializing
最后,让我们反序列化刚刚创建的数据文件。
// Deserialize Users from disk
DatumReader<User> userDatumReader = new SpecificDatumReader<User>(User.class);
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"}
反序列化与序列化非常相似。我们创建一个SpecificDatumReader,类似于序列化中使用的SpecificDatumWriter,它将内存中的序列化项目转换为生成的类(在本例中为User)的实例。我们将DatumReader和先前创建的File传递到DataFileReader,类似于DataFileWriter,后者读取写入器使用的schema以及磁盘上文件中的数据。将使用文件中包含的writer的schema和reader提供的schema(在本例中为User类)读取数据。需要writer的shema来了解fields的写入顺序,而需要reader的schema知道需要哪些字段以及如何填写自文件写入以来添加的字段的默认值。如果两个schema之间存在差异,则根据 Schema Resolution specification对其进行解析。
接下来,我们使用DataFileReader遍历序列化的Users,并将反序列化的对象打印到stdout。请注意我们如何执行迭代:创建一个User对象,将当前反序列化的用户存储在其中,并将此记录对象传递给dataFileReader.next的每次调用。这是一种性能优化,它允许DataFileReader重复使用相同的User对象,而不是为每次迭代分配新的User,如果反序列化大数据文件,这在对象分配和垃圾回收方面可能会非常昂贵。尽管此技术是遍历数据文件的标准方法,但如果不考虑性能,也可以将其用于(用户user:dataFileReader)。
Compiling and running the example code
该示例代码作为一个Maven项目包含在Avro文档的examples / java-example目录中。在此目录中,执行以下命令来构建和运行示例:
$ mvn compile # includes code generation via Avro Maven plugin
$ mvn -q exec:java -Dexec.mainClass=example.SpecificMain
Beta feature: Generating faster code
在此版本中,我们引入了一种新的生成代码的方法,该方法可将对象的解码速度提高10%以上,将编码速度提高30%以上(未来性能的增强正在进行中)。为确保将此更改平稳地引入生产系统,此功能由功能标志org.apache.avro.specific.use_custom_coders进行控制。在此第一个版本中,此功能默认情况下处于关闭状态。要打开它,请在运行时将系统标志设置为true。例如,在上面的示例中,您可以启用代码,如下所示:
$ mvn -q exec:java -Dexec.mainClass=example.SpecificMain \
-Dorg.apache.avro.specific.use_custom_coders=true
请注意,您不必重新编译Avro schema即可使用此功能。该功能已编译并内置到您的代码中,您可以在运行时使用功能标志将其打开和关闭。结果,例如,您可以在测试期间将其打开,然后在生产中将其关闭。或者,您可以在生产中将其打开,如果出现故障,可以迅速将其关闭。 我们鼓励Avro社区尽早使用此新功能,以建立信心。 (对于那些按需购买云计算资源的用户,可以节省大量成本。)随着信心的建立,我们将默认打开此功能,并最终消除功能标志(和旧代码)。
Serializing and deserializing without code generation
序列化和反序列化,无需代码生成。
Avro中的数据始终与相应的schema一起存储,这意味着无论我们是否提前知道该schema,我们都可以始终读取序列化的项目。这使我们无需代码生成即可执行序列化和反序列化。 让我们来看与上一部分相同的示例,但是不使用代码生成:我们将创建一些User,将他们序列化为磁盘上的数据文件,然后读回该文件并反序列化users对象。
Creating users
首先,我们使用Parser 读取我们的schema定义并创建一个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");
由于我们不使用代码生成,因此我们使用GenericRecords表示users。 GenericRecord使用该schema来验证我们仅指定有效字段。如果尝试设置不存在的字段(例如,user1.put(“ favorite_animal”,“ cat”)),则在运行程序时会收到AvroRuntimeException。 请注意,我们没有设置user1的favorite_color。由于该记录的类型为[“ string”,“ null”],因此我们可以将其设置为字符串,也可以将其保留为null。它本质上是可选的。
Serializing
现在我们已经创建了user对象,对其进行序列化和反序列化几乎与上面使用代码生成的示例相同。主要区别在于我们使用通用而不是特定的 readers和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,又需要验证是否存在所有非空字段。 与代码生成示例中一样,我们还创建一个DataFileWriter,它将序列化的记录以及schema写入dataFileWriter.create调用中指定的文件中。我们通过调用dataFileWriter.append方法将用户写入文件。完成写入后,我们将关闭数据文件。
Deserializing
最后,我们将反序列化刚刚创建的数据文件。
// 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,类似于我们在序列化中使用的GenericDatumWriter,它将内存中的序列化项目转换为GenericRecords。我们将DatumReader和先前创建的File传递到DataFileReader,类似于DataFileWriter,后者读取writer使用的schema以及磁盘上文件中的数据。数据将使用文件中包含的writer schema和提供给GenericDatumReader的reader schema读取。需要writer的schema来了解字段的写入顺序,而reader的schema则需要知道需要哪些字段以及如何填写自文件写入以来添加的字段的默认值。如果两个schema之间存在差异,则根据 Schema Resolution 规范对其进行解析。 接下来,我们使用DataFileReader遍历序列化的用户,并将反序列化的对象打印到stdout。注意我们如何执行迭代:我们创建一个GenericRecord对象,将当前的反序列化用户存储在其中,并将该记录对象传递给dataFileReader.next的每次调用。这是一项性能优化,它允许DataFileReader重复使用相同的记录对象,而不是为每次迭代分配新的GenericRecord,如果反序列化大数据文件,这在对象分配和垃圾回收方面可能会非常昂贵。尽管此技术是遍历数据文件的标准方法,但如果不考虑性能,也可以将其用于(GenericRecord用户:dataFileReader)。
Compiling and running the example code
该示例代码作为一个Maven项目包含在Avro文档的examples / java-example目录中。在此目录中,执行以下命令来构建和运行示例:
$ mvn compile
$ mvn -q exec:java -Dexec.mainClass=example.GenericMain