知识卡片
Thrift与Protobuf靠不可变的字段标签号实现模式演变
内容
Thrift和Protocol Buffers编码的记录本质是各字段编码值的拼接,每个字段不是靠字段名 标识,而是靠模式里定义的标签号码(如1、2、3)加数据类型注释来标识——这是它们比 未压缩JSON二进制变体更紧凑的关键,也是它们处理模式演变的核心机制。字段标签号一旦 写入编码数据就固定含义,因此可以随意改字段名(编码数据从不引用字段名),但绝不能 改标签号(会让所有现存编码数据失效)。新增字段:只要给它一个新的标签号,旧代码 遇到不认识的标签号就直接跳过(数据类型注释告诉解析器要跳过多少字节)——这保证了 向前兼容(旧代码能读新代码写的数据)。删除字段的处理是新增字段的镜像:只能删除 可选字段(必需字段永远不能删除),而且不能把这个标签号重新分配给别的字段(因为 可能还有旧数据带着这个标签号,新代码必须知道要忽略它)——这保证了向前兼容不被 破坏。向后兼容的关键约束是:模式初始部署之后新增的每个字段必须是可选的或带默认值, 否则新代码要求某字段”必需”,但旧代码从未写过这个字段,读取旧数据时这个必需检查就 会失败。这套”标签号恒定语义+新增必须可选+删除只删可选且不复用标签”的规则组合, 本质是围绕”编码数据只认标签号不认字段名”这一个核心设计决策,系统性地推导出的一整套 兼容性守则。
结构图:
flowchart TD
A[字段标签号: 一旦写入编码数据即固定语义] --> B[可以改字段名: 编码数据不引用字段名]
A --> C[不能改标签号: 会让已有编码数据失效]
D[新增字段] --> D1[必须给新标签号]
D1 --> D2[必须可选或带默认值]
D2 -.保证向后兼容: 新代码读旧数据不因缺字段崩溃.-> D2
E[旧代码读含未知标签的新数据] --> E1[按类型注释跳过, 直接忽略]
E1 -.保证向前兼容.-> E1
F[删除字段] --> F1[只能删可选字段]
F1 --> F2[标签号不可复用]
参考来源
- 位置:《数据密集型应用系统设计》第四章《编码与演化》"字段标签和模式演变"
(源文件:_epub-src/ch4_split_001.html)
- 结论依据:原文说明编码记录由标签号标识字段而非字段名,因此可改字段名但不能改
标签号,新增字段需要新标签号且必须可选或有默认值以保持向后兼容,删除字段只能
删可选字段且不能复用标签号以保持向前兼容,直接支撑本卡片的结构梳理。
- 原始内容:您可以更改架构中字段的名称,因为编码的数据永远不会引用字段名称,但
不能更改字段的标记,因为这会使所有现有的编码数据无效……为了保持向后兼容性,
在模式的初始部署之后添加的每个字段必须是可选的或具有默认值……你不能再次使用
相同的标签号码。