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 序列化的完整流程。下面这张图可以帮你建立整体认知:
流程其实很简单:
- 序列化:数据对象 + Schema → Avro 序列化器 → 二进制字节流。
- 反序列化:二进制字节流 + 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 集成上更自然。