2336 字
约 7 分钟
16
S06-A20 · 发布顺序:多模块系统里,谁必须先发

一次发布,五个服务里有三个改了代码。团队"一起发"——结果订单服务先起来,而它依赖的"库存服务新接口"还没上,于是下单全量失败了两分钟。复盘时说"以后按顺序发",这个"没有结论"很常见,因为顺序看起来是"经验问题"(谁先谁后有讲究)。而实际上它是"兼容性方向"的推论——一旦写出规则,就不必每次讨论。
而"顺序"是什么、凭什么定,没有结论。

多模块/多服务的发布顺序不是"经验",而是由"兼容性方向"决定:先发"能被旧版调用"的那一方,再发"开始使用新接口"的那一方。这个规则能覆盖绝大多数情况,而它是可推导的。


1. 单个改动的"两端"

端 角色
提供方(Provider) 新增/修改接口
消费方(Consumer) 调用新接口

顺序固定:先发布"提供方的新版本(同时兼容旧调用)",再发布"消费方的新版本"。这样任何时刻"新旧混跑"都能工作。

反过来做就是"消费方先发"——于是它调用一个还不存在的接口,直接失败。第二项(改语义)不能靠顺序解决:两个版本的"同一接口"含义不同,无论谁先发,混跑期都会有一方理解错。所以它必须"并存两套",而不是"排个顺序"。
而这次失败会持续到"提供方发布完成",即两分钟到两小时。

2. 三种兼容策略与发布顺序

策略 顺序
加字段/加接口(向后兼容) 提供方先发,消费方后发
改语义(不兼容) 需"并行版本"三步走
删接口(破坏性) 消费方先迁移,最后删

第二项不能靠"顺序"解决,只能靠"扩展-收缩":先并存两套(新旧都支持),再迁移消费方,最后删旧的。

第三项的"证据"要求最严:"删接口"是不可逆的,而"我以为没人用"是错觉的高发区——所以它必须靠"按接口统计调用量"而不是"记忆"。
第三项的顺序与第一项相反:消费方先停止使用,提供方才能删——而"停止使用"要靠"确认无调用"(日志统计),不能靠"我记得没有了"。

3. "一起发"为什么必然出问题

原因 说明
发布不是原子 实例逐个替换,中间态存在
回滚不是原子 回滚后又是另一个中间态
健康检查滞后 新实例"就绪"不等于"依赖可用"

第一项最根本:滚动发布天然产生"新旧并存"的窗口第一项的"推论"是"任何一次发布都必须考虑混跑"——包括"回滚":回滚也是"新旧并存",只是方向相反。所以"回滚方案"也要按"混跑安全"设计。
,于是"必须考虑混跑"。而"一起发"的隐含假设是"要么全新、要么全旧"——这个假设不成立。

// 滚动发布的中间态(3 实例,依次替换)
t0: [旧][旧][旧]
t1: [新][旧][旧]   ← 混跑:新调旧、旧调新,都要能工作
t2: [新][新][旧]   ← 混跑
t3: [新][新][新]
// 结论:任何一次发布都要保证"t1、t2 的状态可用"

4. 三步走的具体做法

步骤 内容
1 扩展 提供方同时支持新旧,消费方不变
2 迁移 消费方切到新接口,旧路径保留
3 收缩 确认无调用后,提供方删旧

第三步的"确认"必须有数据:按接口/字段统计调用量,连续 N 天为 0。而"我看日志里没有"不足以支撑"删"。

// 发布单里"顺序"的显式表达(三行)
step_1: 库存服务 v2.3(新增 /stock/lock,兼容旧 /stock/deduct)
step_2: 订单服务 v1.9(切到 /stock/lock,保留 fallback 到 /stock/deduct)
step_3: 库存服务 v2.4(28 天后删 /stock/deduct,需 0 调用证据)

"混跑安全"要用"双向兼容"检查一遍,而不是只看"新调旧":

// 三项检查(发布前,逐项过)
1 新提供方 + 旧消费方:旧调用还能成功吗(接口/字段/语义)
2 旧提供方 + 新消费方:新调用会被拒绝吗(若会,不能先发消费方)
3 数据层:新旧两个版本读写同一份数据时,格式都兼容吗
// 第 3 项最容易被漏:代码兼容了,数据格式不兼容

5. 怎么定"整个发布"的顺序

方法 说明
按依赖图拓扑排序 提供方在前
按"改动类型"分组 兼容类先、不兼容类后
按"回滚影响"排序 影响小的先

第一项在"图有环"时失效:A 调 B、B 调 A(哪怕是间接),拓扑排序无解。此时要么"合并发布",要么"用扩展-收缩把环打断"。
第一项是"机械可算"的:把"调用关系"画成有向图,拓扑排序即可。而它需要"依赖图是最新的"——这在微服务里通常靠"服务注册 + 调用链数据"。

第三项是"风险控制":先发"回滚代价小"的服务,于是"出问题时只回滚一个"。第三项还有一个"观测"理由:把"影响面小"的服务先发,可以先用它验证"新版本的构建/配置/依赖"是否正常——于是"大问题"在"小服务"上先暴露。

"回滚"也要按"混跑安全"设计,而它的顺序是"发布的逆序":

// 一次发布与回滚的完整顺序(依赖图:库存 ← 订单)
发布:库存 v2.3(兼容)→ 订单 v1.9(用新接口)→ 库存 v2.4(删旧接口)
回滚:库存 v2.4 → v2.3(先恢复兼容)→ 订单 v1.9 → v1.8 → 库存不动
// 关键:删旧接口那一步回滚不了 → 所以"删"应当"至少晚 28 天",与发布分离

6. 排错:五种"发布顺序问题"

现象 成因 处理方向
发布后立刻大量报错 消费方先发 按"提供方先"重排
部分实例异常 混跑窗口不兼容 加"双版本兼容"
回滚后又出错 回滚顺序错 按逆序回滚
删接口后仍有调用 未确认无调用 先统计再删
间歇性失败 依赖版本未对齐 核对同批版本矩阵

第四项的"实现"要能"按版本"统计:同一个接口,v1 与 v2 的调用量要分开。否则"总量在降"但"旧版仍有调用"会被掩盖。
第四项很常见:"我以为没人用" 与"确实没人用"之间,差一次"按接口统计调用量"。

"顺序"的落地形式是"发布单里的三段",它让"顺序"从"口头约定"变成"可检查的记录":

// 发布单(多服务改动时必填)
changes:
  - svc: inventory  ver: 2.3  type: add-api        # 兼容
  - svc: order      ver: 1.9  type: use-new-api
  - svc: inventory  ver: 2.4  type: remove-api     # 破坏性,28 天后
order: [[email protected], [email protected]]                  # 本批实际发布的顺序
deferred: [[email protected]]                          # 明确"不在本批"
rollback: "逆序回滚到 2.3/1.8,2.4 未发布故无需回滚"

7. 边界:什么情况顺序无所谓

场景 为什么 做法
服务间无调用(独立功能) 无依赖 并行发
全程向后兼容(只加字段) 混跑安全 并行发
单体应用(一次部署) 无跨服务依赖 顺序无关
有"服务网格"做路由金丝雀 可按流量切 顺序可宽松

判断口径一句话:这次改动会不会让"新旧两个版本"在混跑时无法工作。最后一条与"组织"有关的边界:"发布顺序"必须有人负责。因为"每个服务各自发布"时,没人看"整体顺序"——于是"顺序"在所有团队都做对的情况下依然可能是错的。
会(改协议、改语义、删接口),就必须定顺序;不会(只加字段),并行发是安全的。


8. 自测

  1. 发布顺序由什么决定?为什么"提供方先发"能让混跑安全?
  2. "改语义"为什么不能靠顺序解决?"扩展-收缩"三步分别做什么?
  3. "一起发"为什么必然出问题?滚动发布的中间态是什么?
  4. 第三步"收缩"需要什么证据?为什么"我看日志里没有"不够?
  5. "按回滚影响排序"的作用是什么?
  6. 判断"顺序是否重要"的那个问题是什么?
S06-A20 · 发布顺序:多模块系统里,谁必须先发
https://www.clxhxhhr.top/posts/6575/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。