Apache Avro简介,java实现官网翻译

Apache Avro™

Introduction

Apache Avro™ 是一个数据序列化系统。

Avro 提供:

  • 丰富的数据结构。

  • 一种紧凑、快速的二进制数据格式。

  • 一个容器文件,用于存储持久数据。

  • 远程过程调用 (RPC)。

  • 与动态语言的简单集成。 代码生成不需要读取或写入数据文件,也不需要使用或实现 RPC 协议。 代码生成作为一种可选的优化,只值得为静态类型语言实现。

Schemas

Avro 依赖于schemas。读取 Avro 数据时,写入时使用的schemas始终存在。这允许在没有每个值开销的情况下写入每个数据,从而使序列化既快速又小。这也便于使用动态脚本语言,因为数据及其模式是完全自描述的。

当 Avro 数据存储在文件中时,它的schemas也随之存储,以便任何程序稍后可以处理文件。如果读取数据的程序需要不同的schemas,这很容易解决,因为两种schemas都存在。

在 RPC 中使用 Avro 时,客户端和服务器在连接握手中交换schemas。 (这可以优化,以便对于大多数调用,实际上不传输模式。)由于客户端和服务器都具有对方的完整schemas,因此可以轻松解决相同命名字段之间的对应关系、缺失字段、额外字段等问题.

Avro 模式是用 JSON 定义的。这有助于在已有 JSON 库的语言中实现。

Comparison with other systems

Avro 提供类似于 Thrift、Protocol Buffers 等系统的功能。Avro 在以下基本方面与这些系统不同。

动态类型:Avro 不需要生成代码。 数据总是伴随着一个模式,允许在没有代码生成、静态数据类型等的情况下完全处理该数据。这有助于构建通用数据处理系统和语言。

未标记数据:由于schemas在读取数据时存在,因此需要用数据编码的类型信息要少得多,从而导致更小的序列化大小。

没有手动分配的字段 ID:当schemas更改时,处理数据时旧架构和新架构始终存在,因此可以使用字段名称以符号方式解决差异。

JAVA简单使用

这是使用 Java 开始使用 Apache Avro™ 的简短指南。 本指南仅涵盖使用 Avro 进行数据序列化; 请参阅 Patrick Hunt 的 Avro RPC 快速入门,了解如何使用 Avro for RPC。

可以从 Apache Avro™ 发布页面下载 C、C++、C#、Java、PHP、Python 和 Ruby 的 Avro 实现。 本指南使用 Avro 1.10.2,即撰写本文时的最新版本。 对于本指南中的示例,请下载 avro-1.10.2.jar 和 avro-tools-1.10.2.jar。

或者,如果您使用 Maven,请将以下依赖项添加到您的 POM:

<dependency>
  <groupId>org.apache.avro</groupId>
  <artifactId>avro</artifactId>
  <version>1.10.2</version>
</dependency>

以及 Avro Maven 插件(用于执行代码生成):

<plugin>
  <groupId>org.apache.avro</groupId>
  <artifactId>avro-maven-plugin</artifactId>
  <version>1.10.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.8</source>
    <target>1.8</target>
  </configuration>
</plugin>

您还可以从源代码构建所需的 Avro jar。 构建 Avro 超出了本指南的范围; 有关更多信息,请参阅 wiki 中的构建文档页面。

Defining a schema

Avro schema是使用 JSON 定义的。 schema由原始类型(null、boolean、int、long、float、double、bytes 和 string)和复杂类型(record、enum、array、map、union 和 fixed)组成。 您可以从规范中了解有关 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定义了一个代表用户的记录。 (请注意,schema文件只能包含一个schema定义。)至少,记录定义必须包括其类型(“type”:“record”)、名称(“name”:“User”)和字段, 在本例中,名称、收藏夹编号和收藏夹颜色。 我们还定义了一个命名空间(“namespace”:“example.avro”),它与 name 属性一起定义了schema的“全名”(在本例中为 example.avro.User)。

字段是通过对象数组定义的,每个对象都定义了名称和类型(其他属性是可选的,有关详细信息,请参阅记录规范)。 字段的类型属性是另一个schema对象,它可以是原始类型或复杂类型。 例如,我们的 User schema的 name 字段是原始类型字符串,而 favorite_number 和 favorite_color 字段都是联合,由 JSON 数组表示。 联合是一种复杂类型,可以是数组中列出的任何类型; 例如,favorite_number 可以是 int 或 null,本质上使其成为可选字段。

Serializing and deserializing with code generation

Compiling the schema

代码生成允许我们根据我们之前定义的模式自动创建类。 一旦我们定义了相关的类,就不需要在我们的程序中直接使用模式。 我们使用 avro-tools jar 生成代码如下:

java -jar /path/to/avro-tools-1.10.2.jar compile schema <schema file> <destination>

这将根据提供的目标文件夹中的架构命名空间在包中生成适当的源文件。 例如,要从上面定义的模式在包 example.avro 中生成一个 User 类,请运行

java -jar /path/to/avro-tools-1.10.2.jar compile schema user.avsc

请注意,如果您使用 Avro Maven 插件,则无需手动调用架构编译器; 该插件会自动对配置的源目录中存在的任何 .avsc 文件执行代码生成。

Creating 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();

如本例所示,可以通过直接调用构造函数或使用构建器来创建 Avro 对象。 与构造函数不同,构建器将自动设置模式中指定的任何默认值。 此外,构建器在设置数据时对其进行验证,而直接构造的对象在对象序列化之前不会导致错误。 但是,直接使用构造函数通常会提供更好的性能,因为构建器会在写入数据结构之前创建数据结构的副本。

请注意,我们没有设置 user1 最喜欢的颜色。 由于该记录的类型为 [“string”, “null”],我们可以将其设置为字符串或将其保留为空; 它本质上是可选的。 同样,我们将 user3 最喜欢的数字设置为 null(使用构建器需要设置所有字段,即使它们为 null)。

Serializing

现在让我们将我们的用户序列化到磁盘。

// 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 类与生成的类一起使用,并从指定的生成类型中提取模式。

接下来,我们创建一个 DataFileWriter,它将序列化记录以及架构写入 dataFileWriter.create 调用中指定的文件。 我们通过调用 dataFileWriter.append 方法将用户写入文件。 完成写入后,我们关闭数据文件。

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);
}

This snippet will output:

{"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,它读取写入器使用的模式以及磁盘上文件中的数据。将使用文件中包含的编写器模式和读取器提供的模式(在本例中为 User 类)读取数据。编写者的模式需要知道写入字段的顺序,而读者的模式需要知道预期的字段以及如何为自文件写入以来添加的字段填写默认值。如果两个架构之间存在差异,则根据架构解析规范进行解析。

接下来我们使用 DataFileReader 遍历序列化的用户并将反序列化的对象打印到标准输出。请注意我们如何执行迭代:我们创建了一个单独的 User 对象,我们将当前反序列化的用户存储在其中,并将此记录对象传递给 dataFileReader.next 的每次调用。这是一种性能优化,允许 DataFileReader 重用相同的 User 对象,而不是为每次迭代分配一个新的 User,如果我们反序列化一个大数据文件,这在对象分配和垃圾收集方面可能非常昂贵。虽然此技术是遍历数据文件的标准方法,但如果不考虑性能,也可以使用 for (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。 例如,在上面的示例中,您可以按如下方式启用 fater 编码器:

$ 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 一起存储,这意味着我们总是可以读取序列化的项目,无论我们是否提前知道模式。 这允许我们在不生成代码的情况下执行序列化和反序列化。

让我们回顾与上一节相同的示例,但不使用代码生成:我们将创建一些用户,将它们序列化为磁盘上的数据文件,然后读回文件并反序列化用户对象。

Creating users

首先,我们使用解析器来读取我们的模式定义并创建一个 Schema 对象。

Schema schema = new Schema.Parser().parse(new File("user.avsc"));

使用此模式,让我们创建一些用户。

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 来表示用户。 GenericRecord 使用模式来验证我们是否只指定了有效的字段。 如果我们尝试设置一个不存在的字段(例如,user1.put(“favorite_animal”, “cat”)),我们将在运行程序时得到一个 AvroRuntimeException。

请注意,我们没有设置 user1 最喜欢的颜色。 由于该记录的类型为 [“string”, “null”],我们可以将其设置为字符串或将其保留为空; 它本质上是可选的。

Serializing

现在我们已经创建了我们的用户对象,序列化和反序列化它们几乎与上面使用代码生成的示例相同。 主要区别在于我们使用泛型而不是特定的读者和作者。

首先,我们将用户序列化为磁盘上的数据文件。

// 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。 它需要模式来确定如何编写 GenericRecords 并验证所有不可为空的字段是否存在。

在代码生成示例中,我们还创建了一个 DataFileWriter,它将序列化记录和架构写入 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);

This outputs:

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

反序列化与序列化非常相似。我们创建了一个 GenericDatumReader,类似于我们在序列化中使用的 GenericDatumWriter,它将内存中的序列化项目转换为 GenericRecords。我们将 DatumReader 和之前创建的 File 传递给 DataFileReader,类似于 DataFileWriter,它读取写入器使用的模式以及磁盘上文件中的数据。将使用文件中包含的作者模式读取数据,并将读者模式提供给 GenericDatumReader。编写者的模式需要知道写入字段的顺序,而读者的模式需要知道预期的字段以及如何为自文件写入以来添加的字段填写默认值。如果两个架构之间存在差异,则根据架构解析规范进行解析。

接下来,我们使用 DataFileReader 遍历序列化的用户并将反序列化的对象打印到标准输出。请注意我们如何执行迭代:我们创建了一个 GenericRecord 对象,我们将当前反序列化的用户存储在其中,并将此记录对象传递给 dataFileReader.next 的每次调用。这是一种性能优化,它允许 DataFileReader 重用相同的记录对象,而不是为每次迭代分配一个新的 GenericRecord,如果我们反序列化一个大数据文件,这在对象分配和垃圾收集方面可能非常昂贵。虽然此技术是遍历数据文件的标准方法,但如果性能不是问题,也可以使用 for (GenericRecord user : dataFileReader)。

Compiling and running the example code

此示例代码作为 Maven 项目包含在 Avro 文档的 examples/java-example 目录中。 从此目录中,执行以下命令来构建和运行示例:

$ mvn compile
$ mvn -q exec:java -Dexec.mainClass=example.GenericMain
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值