知识卡片

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) - 结论依据:原文说明编码记录由标签号标识字段而非字段名,因此可改字段名但不能改 标签号,新增字段需要新标签号且必须可选或有默认值以保持向后兼容,删除字段只能 删可选字段且不能复用标签号以保持向前兼容,直接支撑本卡片的结构梳理。 - 原始内容:您可以更改架构中字段的名称,因为编码的数据永远不会引用字段名称,但 不能更改字段的标记,因为这会使所有现有的编码数据无效……为了保持向后兼容性, 在模式的初始部署之后添加的每个字段必须是可选的或具有默认值……你不能再次使用 相同的标签号码。