4. Schema 兼容性规则:Backward、Forward、Full、None 详解
兼容性规则,说白了就是 Schema 演进的「交通规则」。你改了 Schema,消费者能不能继续跑?生产者能不能发新数据?这些规则就是用来回答这两个问题的。
我刚开始接触 Schema Registry 时,觉得这玩意儿不就是个版本管理吗?后来线上出过一次事故——改了字段类型,老消费者直接反序列化失败。嗯,从那以后,我再也不敢轻视兼容性配置了。
4.1 为什么需要兼容性规则?
先看一个场景。你有个用户信息 Schema:
{
"type": "record",
"name": "User",
"fields": [
{"name": "id", "type": "int"},
{"name": "name", "type": "string"}
]
}
某天你加了个 email 字段。新生产者开始发带 email 的数据。但老消费者还在用旧 Schema 读数据——它不认识 email,直接报错。
这就是兼容性问题。兼容性规则就是告诉 Schema Registry:什么样的变更允许,什么样的变更禁止。
4.2 四种兼容性类型
Avro 和 Schema Registry 定义了四种兼容性类型。我按使用频率排序来讲。
| 类型 | 含义 | 消费者兼容 | 生产者兼容 | 使用场景 |
|---|---|---|---|---|
| BACKWARD | 向后兼容 | 新 Schema 可读旧数据 | 旧 Schema 可读新数据 | 最常用,默认规则 |
| FORWARD | 向前兼容 | 旧 Schema 可读新数据 | 新 Schema 可读旧数据 | 消费者先升级的场景 |
| FULL | 完全兼容 | 双向兼容 | 双向兼容 | 严格管控的 Schema |
| NONE | 不检查兼容性 | 不保证 | 不保证 | 开发/测试环境 |
4.3 BACKWARD 向后兼容(默认规则)
这是 Schema Registry 的默认配置。我个人习惯在生产环境用这个。
规则: 新 Schema 必须能读取旧 Schema 写入的数据。
说白了,消费者可以先升级,生产者后升级。你想想看,这符合大多数系统的升级节奏——先改读的,再改写的。
允许的操作:
- 删除字段(消费者忽略多余字段)
- 添加可选字段(带默认值)
- 给已有字段添加默认值
- 将字段类型放宽(int → long)
禁止的操作:
- 删除带默认值的字段(消费者找不到字段会报错)
- 添加必填字段(无默认值)
- 将字段类型收窄(long → int)
4.4 FORWARD 向前兼容
向前兼容和向后兼容正好反过来。
规则: 旧 Schema 必须能读取新 Schema 写入的数据。
什么时候用?当消费者升级滞后于生产者时。比如你有一个数据管道,生产者先改,消费者慢慢跟上。
允许的操作:
- 添加字段(消费者忽略不认识的新字段)
- 删除可选字段
- 将字段类型收窄
禁止的操作:
- 删除必填字段
- 添加必填字段
- 将字段类型放宽(旧消费者可能读到放大的值)
4.5 FULL 完全兼容
FULL 是 BACKWARD + FORWARD 的合集。双向兼容。
规则: 新 Schema 能读旧数据,旧 Schema 也能读新数据。
这听起来很完美,但限制也最多。你想想看,既要能加字段,又要能删字段,还要保证两边都能读——基本上只能做「添加可选字段」这种温和操作。
允许的操作:
- 添加可选字段(带默认值)
- 删除可选字段(带默认值)
- 重新排序字段(Avro 按名称匹配,不依赖顺序)
禁止的操作:
- 添加或删除必填字段
- 修改字段类型
- 修改字段名称
4.6 NONE 不检查兼容性
NONE 就是「随便改,不检查」。听起来很爽,但风险也最大。
规则: 任何变更都允许,Schema Registry 只做版本号递增,不校验兼容性。
什么时候用?
- 开发环境、测试环境
- 一次性数据导入任务
- 内部工具,不对外暴露
什么时候千万别用?
- 生产环境,尤其是跨团队协作
- 有长期存储的历史数据
- 消费者升级周期长
4.7 兼容性规则对比图
下面这张图展示了四种兼容性规则的核心差异。我把它画成了流程图,方便你理解。
4.8 如何选择兼容性规则?
我一般按这个思路来选:
- 默认选 BACKWARD —— 90% 的场景都适用。消费者先升级,生产者后升级,符合大多数系统的迭代节奏。
- 核心公共 Schema 选 FULL —— 被几十个服务引用的 Schema,变更成本极高。FULL 能强制你走评审流程。
- 消费者升级滞后选 FORWARD —— 比如你有一个数据管道,生产者先改,消费者慢慢跟上。
- 开发/测试环境用 NONE —— 快速迭代,不卡流程。但上线前一定要改成 BACKWARD 或 FULL。
4.9 兼容性检查的底层逻辑
Schema Registry 是怎么检查兼容性的?说白了就是对比两个 Schema 的读写能力。
Avro 的 Schema 兼容性检查基于一个核心机制:Reader Schema 和 Writer Schema。
- Writer Schema:生产者写入数据时用的 Schema
- Reader Schema:消费者读取数据时用的 Schema
兼容性检查就是看:给定 Writer Schema 和 Reader Schema,Avro 能否正确反序列化。
举个例子:
// 旧 Schema(Writer)
{
"type": "record",
"name": "User",
"fields": [
{"name": "id", "type": "int"},
{"name": "name", "type": "string"}
]
}
// 新 Schema(Reader)- 添加了 email 字段,带默认值
{
"type": "record",
"name": "User",
"fields": [
{"name": "id", "type": "int"},
{"name": "name", "type": "string"},
{"name": "email", "type": "string", "default": ""}
]
}
新 Reader 读旧 Writer 的数据时,email 字段没有值,Avro 会用默认值 "" 填充。这就是 BACKWARD 兼容。
反过来,如果新 Writer 写了带 email 的数据,旧 Reader 读的时候,Avro 会忽略不认识的字段。这就是 FORWARD 兼容。
4.10 避坑指南
最后分享几个我踩过的坑:
- 别改字段名 —— Avro 按名称匹配字段。改名相当于删旧字段加新字段,兼容性检查大概率失败。
- 别删带默认值的字段 —— 你以为有默认值就安全?错。删除字段后,老数据里这个字段还在,但新 Schema 不认识,反序列化会报错。
- 类型转换要谨慎 —— int 转 long 是安全的(BACKWARD),但 long 转 int 就不行。string 转 bytes 也不行。
- 枚举类型最坑 —— 枚举加新值是 FORWARD 兼容,但删除枚举值是 BREAKING CHANGE。我建议枚举尽量用 string 替代。