bigywater.tech
Kafka 内部原理 · 71

Schema Registry:消息结构如何演进

不依赖Confluent Schema Registry服务,直接用Avro原生API真实验证它背后的兼容性检查规则:新增字段带不带default值,决定了老数据能不能被新schema正常读取。

带default→成功
兼容的schema演进真实的读取结果
无default→SchemaResolutionError
不兼容的schema演进真实的失败原因

验证:兼容的演进——新增字段带上default值

命令 · python schema_v1 = {"fields": [{"name": "user_id", "type": "long"}, {"name": "name", "type": "string"}]} record_v1 = {"user_id": 1, "name": "Alice"} # 真实用v1写入 schema_v2 = {"fields": [..., {"name": "email", "type": "string", "default": "unknown@example.com"}]} # 真实用v2(带default)读取v1写的数据
真实实测 · 本机执行输出
真实用v1写入: {'user_id': 1, 'name': 'Alice'} -> 字节: b'\x02\nAlice'
真实用v1写的数据,真实用v2(新增带default字段)读: 成功 -> {'user_id': 1, 'name': 'Alice', 'email': 'unknown@example.com'}

v1写入的原始字节里根本没有email这个字段,但用v2真实读取时,Avro的schema resolution机制会真实发现读schema比写schema多了一个字段,检查这个字段有没有default值——有,就真实用default值填上,读取正常完成。这正是Schema Registry里"BACKWARD"兼容性规则真实检查的内容:新schema能不能读懂用旧schema写的数据。

验证:不兼容的演进——新增字段没有default值

命令 · python schema_v2_bad = {"fields": [..., {"name": "email", "type": "string"}]} # 真实没有default
真实实测 · 本机执行输出
真实用v1写的数据,真实用v2(新增无default必填字段)读: 失败 ->
SchemaResolutionError('No default value for field email in User')
真实的错误信息直接点明了问题所在:"No default value for field email"——新schema要求每条记录都必须有email这个字段,但v1写的老数据里压根没有,也没有default值可以拿来填补这个空缺,读取直接失败。这正是Schema Registry真实存在的价值:它在"注册新schema"这一步就会真实用这条兼容性规则做检查,一旦你尝试注册一个会导致老数据读取失败的新schema,注册请求本身就会被真实拒绝,而不是等到生产环境里某个consumer真的读到老数据时才真实炸掉。本文没有真实起一个Confluent Schema Registry服务,但registry真实做的核心检查逻辑,就是本文用Avro原生API直接验证的这条规则。

分步演示:兼容与不兼容的演进真实差在哪

每一步都是本文上面用fastavro跑出来的原始结果。

有没有default值,决定了老数据能不能被新schema读懂
这一步新验证的兼容性场景

    参考与说明

    • 数据来自本机Python的fastavro库(不依赖真实的Confluent Schema Registry服务,直接调用Avro的schema resolution API验证兼容性规则本身)。
    • 没有验证:真实的Schema Registry服务(HTTP API、版本管理、FORWARD/FULL等其它兼容性模式);Protobuf/JSON Schema的兼容性规则跟Avro有哪些不同。
    京ICP备2026067502号-1京公网安备11010502063487号