本节摘要:把三条铁律细化成一张可执行的变更操作分级表——安全、可协商、禁止三级,覆盖扩位、收位、枚举加值、string 与 bytes 互转、repeated 互转等全部高频操作。重点深挖三个争议项:int32→int64 的不对称安全、枚举新增值的降级策略、以及"删字段的正确姿势"。读完你应当能在变更评审里直接引用本表做裁决。
上一节立了法,本节执法。这张表的每个裁决都能用第 3 章的编码机理复核——复核方法比结论本身更值得带走。
先给全表,再逐个深挖争议项。安全=新旧两端双向互读;可协商=单方向安全或需配合迁移动作;禁止=静默错位或数据损坏。
| 变更操作 | 裁决 | 机理要点 |
|---|---|---|
| 字段改名 | 安全 | 名字不进字节流 |
| int32 → int64 | 安全 | varint 同族自然扩位 |
| int64 → int32 | 可协商 | 超出 32 位时静默截断 |
| 新增字段(全新编号) | 安全 | 旧端按未知字段跳过 |
| 删除字段(走 reserved) | 安全 | 新端读旧数据跳过、旧端读新数据缺省 |
| enum 新增枚举值 | 可协商 | 旧端须有未知值降级分支 |
| enum 删除枚举值 | 可协商 | 编号进 reserved,同字段编号规则 |
| string ↔ bytes | 可协商 | LEN 同形态,但语义契约分野 |
| repeated ↔ singular | 可协商 | 见 5.1 推导,方向不对称 |
| 改字段编号 | 禁止 | 等价换字段,静默错位 |
| int ↔ sint | 禁止 | 同 tag 不同载荷解读,无声错位 |
| varint ↔ 定长(int32 ↔ fixed32) | 禁止 | wire type 变更 |
| 偷换字段语义 | 禁止 | 对存量数据撒谎 |
| 新增 oneof 分支字段 | 可协商 | 旧端把新分支当未知字段 |
扩位为什么安全:两者同属 varint 族,旧端发的 32 位值本来就是合法的 64 位 varint(值域嵌套)。反向收位为什么只算可协商:旧端发来的 64 位大值,新端按 int32 读,多出的字节不是报错而是被静默截断——具体行为依实现而定,常见的是值被截成低位 32 位(数值错乱)或被归入未知字段(字段消失感)。工程结论:字段值域有增长可能时一步到位用 int64,永远不要指望将来"再扩"——将来扩位时你面对的是已落库的历史数据,而历史数据的 int64 值在收窄后的系统里没有安全的家。
新增枚举值技术上"安全"——新端发的值旧端按未知字段处理?不完全。wire type 同为 varint,数值本身能被旧端读出,只是不认识这个名字。此时旧端代码里 switch 枚举值会落到 default 或 else 分支——如果这个分支写的是"报错丢弃",新值就等于触发旧端故障。所以裁决是可协商:加枚举值的同时,确认全部旧消费者的 default 分支是优雅降级(记日志、按 UNSPECIFIED 处理、透传未知数值而非丢弃)。这条义务在跨团队契约里要显式写进演进规范——发布方无法替消费方检查分支逻辑,只能靠约定。附带的纪律:enum 编号同样进 reserved,删除 PHASE_ARCHIVE 后编号 4 必须封存,重用会让旧数据的 4 号值瞬间改换语义。
删字段不是一行删掉那么简单,正确姿势分四步:
第一步:proto 里给字段标 deprecated(生成代码开始产生编译警告) 第二步:观察期(一至两个季度)——监控该字段的所有读写方流量归零 第三步:写侧全部下线后,物理删除字段行,编号与名字进 reserved 第四步:reserved 永久保留,绝不解封
第三步的"写侧归零"验证有现成工具:第 8 章 buf 的 breaking 检测能在 CI 里持续校验契约演进,而字段流量可以用 decode_raw 抽样线上字节流统计编号出现率——又是一次"考古方法直接服务工程"的闭环。
⚠️ 最容易被跳过的是第二步。写侧没归零就物理删除,等于主动制造"字段消失悬案"——那些还在写旧字段的存量服务发出的数据,将被所有新端按未知字段静默忽略,数据无声丢失。
把全表收敛成一分钟可走完的评审清单:

背景:风控系统的事件等级枚举从四级加到五级,发布两周后营销侧服务开始少量报"未知的等级值"异常。操作复盘:枚举加值当天,风控侧按流程通知了两个已知消费方(都做了降级);第三个消费方是营销侧通过数据管道间接消费——消息先落 Kafka,营销侧的消费者从订阅启动时拉的是最新 schema 生成的新解析器,但它的业务代码里等级判断是显式 switch 五个已知值、default 抛异常。新增的第五级值在灰度小流量里出现,异常率千分之三。结果:营销侧补上 default 降级(记日志按最低等级处理)后恢复。解读:枚举加值的降级义务必须覆盖全部间接消费路径——数据管道让"消费方"的边界变得模糊,schema 的每个消费者都要默认"未来会有我不认识的值"。变式:对枚举敏感的核心链路,可以要求新增枚举值与灰度发版联动——先发全部消费方的降级代码、再开始产生新值,把"可协商"升级成"双向安全"。
铁律与边界都立好了,还剩保障体系的最后一块:未知字段机制本身——它怎么存、怎么传、什么情况下会丢。