Avro 序列化基础:从 Schema 说起

好,我们正式开始接触 Avro。说实话,我第一次接触 Avro 是在一个实时数据管道项目里。当时团队在用 Kafka,消息格式用的是 JSON,结果生产环境一跑,数据量上来后,带宽和解析性能直接崩了。后来换成 Avro,效果立竿见影。所以今天这章,我想带你从 Avro 的核心理念开始,把它的 Schema 定义、类型系统,以及和 JSON、Protobuf 的差异彻底讲透。

Avro 是什么?

Avro 是 Apache 旗下的一个数据序列化框架。它最核心的特点就是:数据自带 Schema。什么意思?你写一条数据,必须同时带上它的结构定义。读数据的人,拿着这个 Schema 就能准确解析,不需要额外约定。

我个人习惯把 Avro 理解为「带说明书的数据」。JSON 虽然也自描述,但它的「说明书」是隐式的,字段名、类型全靠约定。Avro 的 Schema 是显式的,用 JSON 格式写,但解析时是二进制的,紧凑又高效。

核心优势:Avro 的序列化结果是二进制,体积小、速度快。而且它天生支持 Schema 演化——字段可以增删改,只要兼容规则满足,新旧版本就能互通。这在微服务和流式处理中简直是救命稻草。

Schema 定义语法

Avro 的 Schema 是用 JSON 写的。你定义一个 JSON 对象,里面包含 type、name、fields 等字段。看个最简单的例子:

{
  "type": "record",
  "name": "User",
  "namespace": "com.example",
  "fields": [
    {"name": "id", "type": "int"},
    {"name": "name", "type": "string"},
    {"name": "email", "type": ["null", "string"], "default": null}
  ]
}

这里有几个关键点:

  • type:必须是 record,表示这是一个记录类型。
  • name:记录的名字,全限定名由 namespace + name 组成。
  • fields:字段列表,每个字段有 name 和 type。
  • default:可选,用于向后兼容。当新版本增加字段时,旧数据可以填默认值。

嗯,这里要注意:email 字段的类型是 ["null", "string"],这叫联合类型。意思是这个字段可以是 null 也可以是 string。我建议你给所有可能为空的字段都加上 null 选项,否则序列化时会报错。

基本类型与复杂类型

Avro 的类型系统分两大类:基本类型和复杂类型。我整理了一张表,方便你对照:

分类 类型 说明 示例
基本类型 null 空值 null
boolean 布尔值 true
int 32位有符号整数 42
long 64位有符号整数 123456789L
float 32位浮点数 3.14f
double 64位浮点数 2.71828
bytes 字节序列 \u00FF
string UTF-8 字符串 "hello"
复杂类型 record 结构化记录,类似对象 上面 User 示例
enum 枚举,固定值集合 {"type": "enum", "name": "Status", "symbols": ["ACTIVE", "INACTIVE"]}
array 数组,元素类型相同 {"type": "array", "items": "string"}
map 字典,键为 string {"type": "map", "values": "int"}
联合类型 union 多种类型之一 ["null", "string"]

复杂类型里,record 是最常用的。你想想看,我们定义业务对象,比如订单、用户、商品,基本都是 record。而 union 是 Avro 实现可选字段和 Schema 演化的关键。我曾经在一个项目中,因为没给新加的字段设置 default,结果旧数据反序列化时直接抛异常。从那以后,我养成了一个习惯:所有可能为空的字段,都用 ["null", "xxx"] 并配上 default: null。

小技巧:如果你需要表示一个字段可以是多种类型,比如「要么是字符串,要么是整数」,就用 union。但注意,union 里的类型顺序会影响序列化效率,建议把 null 放第一位。

与 JSON / Protobuf 的对比

很多同学会问:我直接用 JSON 不香吗?为什么非要学 Avro?好,我们直接对比一下:

维度 Avro JSON Protobuf
序列化格式 二进制 文本 二进制
数据体积 小(无字段名) 大(带字段名) 小(字段用编号)
解析速度 快 慢 快
Schema 定义 JSON 格式,自带 隐式,无强制 Schema .proto 文件,需编译
Schema 演化 原生支持,规则灵活 需手动兼容 支持,但规则严格
语言绑定 多语言,动态生成 几乎所有语言原生 多语言,需代码生成
适用场景 大数据、流处理、Kafka Web API、配置文件 RPC、微服务间通信

说白了,JSON 最大的问题是体积大、解析慢,而且没有强制的 Schema 约束。你想想看,一个包含 50 个字段的 JSON 对象,每个字段名都要重复传输,带宽浪费很严重。而 Avro 的二进制格式只存字段值,字段名只出现在 Schema 里,Schema 可以单独存储或注册到 Schema Registry。

Protobuf 和 Avro 很像,但有个关键区别:Protobuf 的字段用数字编号标识,Avro 用字段名。这意味着 Avro 的 Schema 更易读,但 Protobuf 的序列化体积更小。不过,Avro 在 Schema 演化方面更灵活——比如你可以删除字段,只要设置 default 就行;Protobuf 则要求字段编号不能变,删除只能用 reserved 关键字。

避坑指南:我曾经在一个项目中,团队选了 Protobuf 做 RPC,但后来需要把数据写入 Kafka 做流处理。结果发现 Protobuf 的 Schema 和 Kafka 的 Schema Registry 集成不如 Avro 自然。最后我们不得不写一层转换逻辑。所以,如果你主要用 Kafka 做消息中间件,我建议优先考虑 Avro。

Avro 序列化流程

理解了 Schema 和类型,我们来看看 Avro 序列化的完整流程。下面这张图可以帮你建立整体认知:

Avro 序列化与反序列化流程 Avro 序列化与反序列化流程 数据对象 Schema 定义 Avro 序列化器 二进制 二进制 Avro 反序列化器 Schema 定义 数据对象 序列化时,Schema 和数据一起写入流;反序列化时,从流中读取 Schema 再解析数据

流程其实很简单:

  1. 序列化:数据对象 + Schema → Avro 序列化器 → 二进制字节流。
  2. 反序列化:二进制字节流 + Schema → Avro 反序列化器 → 数据对象。

关键点在于:Schema 必须双方都知道。在实际的 Kafka 场景中,Schema 会注册到 Schema Registry,生产者只存 Schema ID,消费者通过 ID 拉取 Schema。这样既节省带宽,又保证一致性。

个人经验:我建议你在定义 Schema 时,尽量用 namespace 做包名隔离。比如 com.example.order.v1 和 com.example.order.v2,这样不同版本可以共存,方便灰度迁移。

小结

这一章我们覆盖了 Avro 的核心概念:

  • Avro 是带 Schema 的二进制序列化框架,适合大数据和流处理。
  • Schema 用 JSON 定义,支持基本类型和复杂类型,union 是实现可选字段的关键。
  • 相比 JSON,Avro 体积小、速度快、有 Schema 约束;相比 Protobuf,Avro 在 Schema 演化和 Kafka 集成上更自然。