1. 概述

数据序列化是将内存中的数据结构转换为可存储或传输的二进制或文本格式的技术。市面上有多种序列化方案,Apache Avro 就是其中一种高性能、跨语言的序列化系统。

Avro 是一个基于 Schema、语言无关的数据序列化库。它通过 Schema 来定义数据结构,并使用 JSON 格式描述 Schema,结构清晰且易于维护。

在本教程中,我们将深入探讨:

  • Avro 的 Java API 使用
  • Schema 的创建与管理
  • 序列化与反序列化的完整流程
  • 与其他序列化方案(如 JSON、Protobuf)的对比

重点会放在 Schema 的设计与生成,因为这是 Avro 的核心基础。


2. Apache Avro 简介

Avro 是一个语言无关的序列化框架,其核心是 Schema 驱动。所有数据的读写都依赖 Schema,且 Schema 本身会被嵌入到数据文件的元数据中,实现“自描述”。

✅ 优势总结:

  • 高性能:二进制编码,压缩率高,适合大数据场景
  • 跨语言支持:Java、Python、C++ 等主流语言均有支持
  • Schema 演进友好:支持向后/向前兼容的 Schema 变更
  • 与 Hadoop/Kafka 深度集成:是这些生态中的主流序列化格式

⚠️ 注意:Avro 不会在每个字段中存储类型信息,而是通过 Schema 统一管理,因此更节省空间。


3. 问题定义

我们以一个实际场景为例:构建一个 AvroHttpRequest 类,用于记录 HTTP 请求信息,包含基本类型、复杂对象、集合和枚举。

class AvroHttpRequest {
    
    private long requestTime;
    private ClientIdentifier clientIdentifier;
    private List<String> employeeNames;
    private Active active;
}

其中:

  • requestTime: 基本类型(long)
  • ClientIdentifier: 复杂类型(嵌套对象)
  • employeeNames: 集合类型(List
  • Active: 枚举类型

🎯 目标:使用 Apache Avro 实现该类的序列化与反序列化。


4. Avro 数据类型

Avro 支持两类数据类型:

✅ 基本类型(Primitive Types)

类型 说明
null 空值
boolean 布尔值
int / long 整数
float / double 浮点数
bytes 字节数组
string 字符串

示例:{"type": "string"}

✅ 复杂类型(Complex Types)

类型 说明
record 类似 Java 类,包含多个字段
enum 枚举,定义一组命名符号
array 数组,元素类型固定
map 键值对,键必须是 string,值为任意类型
union 联合类型,如 ["null", "string"] 表示可为空的字符串
fixed 固定长度的字节数组

📌 本例中:

  • ClientIdentifierrecord
  • Activeenum
  • employeeNamesarray

5. 项目依赖配置

使用 Maven 时,需引入以下核心依赖:

<dependency>
    <groupId>org.apache.avro</groupId>
    <artifactId>avro</artifactId>
    <version>1.11.3</version>
</dependency>
<dependency>
    <groupId>org.apache.avro</groupId>
    <artifactId>avro-maven-plugin</artifactId>
    <version>1.11.3</version>
</dependency>

✅ 建议使用最新稳定版,可在 Maven Central 查询。

构建流程概览:

  1. 定义 .avsc Schema 文件
  2. 使用 avro-maven-plugin 自动生成 Java 类
  3. 调用 Avro API 进行序列化/反序列化

6. Schema 创建

Avro 的 Schema 使用 JSON 定义,主要字段包括:

字段 说明
type 类型(record、enum 等)
name 名称
namespace 包名,避免命名冲突
fields 字段列表(仅 record)

6.1 使用 SchemaBuilder 构建 Schema

相比手写 JSON,使用 SchemaBuilder 更安全、可读性更强。

创建 ClientIdentifier Schema:

Schema clientIdentifier = SchemaBuilder.record("ClientIdentifier")
  .namespace("com.example.avro.model")
  .fields()
  .requiredString("hostName")
  .requiredString("ipAddress")
  .endRecord();

创建 AvroHttpRequest Schema:

Schema avroHttpRequest = SchemaBuilder.record("AvroHttpRequest")
  .namespace("com.example.avro.model")
  .fields()
    .requiredLong("requestTime")
    .name("clientIdentifier")
      .type(clientIdentifier)
      .noDefault()
    .name("employeeNames")
      .type()
        .array()
        .items()
        .stringType()
      .arrayDefault(null)
    .name("active")
      .type()
        .enumeration("Active")
        .symbols("YES", "NO")
      .noDefault()
  .endRecord();

⚠️ 注意:clientIdentifier 字段的类型直接引用了之前定义的 Schema 对象。

6.2 验证并导出 Schema

通过 toString() 可将 Schema 对象转为 JSON 字符串:

@Test
void whenCallingSchemaToString_thenReturnJsonAvroSchema() {
    Schema clientIdSchema = clientIdentifierSchema();

    assertThatJson(clientIdSchema.toString())
      .isEqualTo("""
          {
             "type":"record",
             "name":"ClientIdentifier",
             "namespace":"com.example.avro.model",
             "fields":[
                {
                   "name":"hostName",
                   "type":"string"
                },
                {
                   "name":"ipAddress",
                   "type":"string"
                }
             ]
          }
          """);
}

最佳实践:将生成的 Schema 保存为 .avsc 文件,放入 src/main/resources/avro/ 目录,便于后续代码生成。


7. 读取 Schema 并生成 Java 类

有两种方式使用 Schema:

  1. Generic 模式:直接使用 GenericRecord,无需生成类(适合动态场景)
  2. Specific 模式:根据 .avsc 文件生成 Java 类(推荐,类型安全)

使用 avro-maven-plugin 自动生成类

pom.xml 中配置插件:

<plugin>
    <groupId>org.apache.avro</groupId>
    <artifactId>avro-maven-plugin</artifactId>
    <version>1.11.3</version>
    <executions>
        <execution>
            <id>schemas</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>schema</goal>
            </goals>
            <configuration>
                <sourceDirectory>${project.basedir}/src/main/resources/avro/</sourceDirectory>
                <outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

📌 执行 mvn clean compile 后,插件会自动生成:

  • ClientIdentifier.java
  • AvroHttpRequest.java
  • 包含 getClassSchema() 静态方法

8. 序列化与反序列化

Avro 支持两种格式:

  • Binary:紧凑、高效,适合存储和传输
  • JSON:可读性强,适合调试

核心接口:

接口 说明
DatumWriter<T> 写入数据
DatumReader<T> 读取数据
Encoder / Decoder 编码/解码器(Binary 或 JSON)

8.1 序列化(JSON 格式)

public byte[] serializeAvroHttpRequestJSON(AvroHttpRequest request) {
    DatumWriter<AvroHttpRequest> writer = new SpecificDatumWriter<>(AvroHttpRequest.class);
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    byte[] data = new byte[0];
    
    try {
        Encoder jsonEncoder = EncoderFactory.get().jsonEncoder(
            AvroHttpRequest.getClassSchema(), stream);
        writer.write(request, jsonEncoder);
        jsonEncoder.flush();
        data = stream.toByteArray();
    } catch (IOException e) {
        logger.error("Serialization error: " + e.getMessage());
    }
    return data;
}

✅ 测试用例:

@Test
void givenJSONEncoder_whenSerialized_thenObjectGetsSerialized() {
    byte[] data = serializer.serializeAvroHttpRequestJSON(request);
    assertTrue(data != null && data.length > 0);
}

📌 若使用 二进制格式,只需替换 encoder:

Encoder binaryEncoder = EncoderFactory.get().binaryEncoder(stream, null);

8.2 反序列化(JSON 格式)

public AvroHttpRequest deSerializeAvroHttpRequestJSON(byte[] data) {
    DatumReader<AvroHttpRequest> reader = new SpecificDatumReader<>(AvroHttpRequest.class);
    try {
        Decoder decoder = DecoderFactory.get().jsonDecoder(
            AvroHttpRequest.getClassSchema(), new String(data));
        return reader.read(null, decoder);
    } catch (IOException e) {
        logger.error("Deserialization error: " + e.getMessage());
        return null;
    }
}

✅ 测试验证:

@Test
void givenJSONDecoder_whenDeserialize_thenActualAndExpectedObjectsAreEqual() {
    byte[] data = serializer.serializeAvroHttpRequestJSON(request);
    AvroHttpRequest actual = deSerializer.deSerializeAvroHttpRequestJSON(data);
    assertEquals(request, actual);
}

📌 二进制反序列化:

Decoder decoder = DecoderFactory.get().binaryDecoder(data, null);

9. 总结

Apache Avro 是大数据生态中不可或缺的序列化工具,尤其适合 Hadoop、Kafka、Flink 等场景。

✅ 核心优势:

  • 高效紧凑:二进制编码,节省存储与带宽
  • Schema 驱动:数据自描述,支持 Schema 演进
  • 跨语言支持:多语言 SDK 成熟
  • 与 Java 集成友好:通过 Maven 插件自动生成代码,开发效率高

⚠️ 踩坑提醒:

  • Schema 文件命名建议统一为 .avsc,路径清晰
  • 使用 SpecificDatumWriter/Reader 时,务必确保类与 Schema 一致
  • 生产环境优先使用 二进制格式,性能更优

📌 简单粗暴一句话:如果你在做大数据,Avro 值得拥有


原始标题:Guide to Apache Avro | Baeldung