4.1 灰度发布:按版本与流量放量

1.1 的验收表里 R4 写的是「新版本可按 5%、20%、50% 三档放量」,这一篇把它变成可操作的流程:放量档位、观察指标、自动回滚条件、收口动作。灰度不是把新版服务发上线就完事,核心是流量与版本的解耦。灰度策略的更多变体与 go-micro-cps-guide 的发布章节对照,本篇只讲我们三档放量的执行细节。

场景:全量发布的代价

事件 全量发布时 灰度发布后
一次坏版本的影响面 100% 用户 5% 起步
回滚动作 全量重发旧包,12 分钟 比例拨回 0,8s 生效
发现时间 客诉先到,平均 25 分钟 指标自动告警,3 分钟

全量时代的回滚是「换包」,灰度后的回滚是「拨比例」,速度差 100 倍。这是灰度最大的收益,回滚从分钟级变成秒级。

还有一个隐性收益:灰度让「发布」与「上线」解耦。v2 实例可以提前一天发上去挂着 0% 流量预热(缓存热、连接建立、JIT 类预热在 Go 里表现为 GC 与 map 扩容稳定),正式放量时没有冷启动惩罚。3.1 的 warmup 豁免(60s)与这里的预热窗口配合,误熔断率明显下降。

方案:三档放量加自动回滚

档位 比例 观察时长 晋级条件
一档 5% 30 分钟 三看线无告警
二档 20% 2 小时 错误率与 P99 与 v1 持平
三档 50% 至次日低谷 业务指标无异常
收口 100% — 全量加撤 v1

「与 v1 持平」的判定要有数字:错误率 v2 不超 v1 的 1.5 倍且绝对值 < 3%,P99 不超预算的 110%(3.3 的段预算),业务指标(下单成功率、支付回调量)波动在 ±2% 内。三组数字各对应一类风险:技术错误、性能回归、业务回归,缺任一组都可能「指标绿了但业务坏了」。

自动回滚条件(任一命中即拨回 0):v2 实例错误率超 v1 的 2 倍且绝对值超 3%、v2 P99 超预算、v2 出现资金类 err_code。回滚动作是灰度控制器把 gov/gray/{svc} 写成 0,watch 传播后 8 秒全集群切回 v1,v2 实例留 24h 供取证,COS 里制品快照保留 90 天。

回滚要同时处理两种流量:新用户进来直接全走 v1(比例 0),已落在灰度桶内的 in-flight 请求让它自然完成,不强制中断。强制中断在支付链路上出过一次客诉,从那起之后「在途不中断」写进回滚 SOP。

配置与代码:放量在 etcd 里走

// 灰度控制器:比例写在 etcd,LB 侧 watch 即时生效
cli.Put(ctx, "gov/gray/orders", "0.20")
// 调用方 LB 收到事件后,20% 流量进 v=2 桶
// 桶分配:hash(userId) % 100 < 20 命中灰度(稳定 hash)

放量节奏由灰度控制器按观察指标自动晋级:一档观察期三看线全绿就升二档,夜间不发版,一律从当日 10:00 起量。控制器本身双实例热备,状态存 Redis,避免控制器单点。

放量与 2.4 的 LB 层解耦:控制器只写 etcd 比例,LB 只按 hash 分桶,两者不互相感知。解耦的好处是改放量节奏不动 LB 代码,加新的路由维度(比如按地区)只改控制器不动 LB。路由维度后来加过「按城市」一档,给某地区先行开放新功能,维度在控制器里是配置项,不用发版。

灰度期间 3.4 的限流阈值联动也在这里:v2 实例数少,单机阈值临时乘 2,放量收口后随控制器自动还原。这条联动在 3.4 踩过灰度被限的坑之后才加进去。

回滚的完整动作

自动回滚触发后,控制器执行的动作是固定的一套,写死在 SOP 里,值班只确认不临场发挥:

步骤 动作 时长
1 比例写 0,watch 全集群切 v1 8s
2 v2 实例摘流量,保留 24h 取证 即时
3 告警带触发条件与版本快照 即时
4 拉群同步:触发指标、v1 是否接稳 10 分钟
5 COS 快照留存 90 天,发版单标红 24h 内

一次真实回滚的复盘模板也固定:触发条件、命中时刻、影响时长、误判风险(该不该回)、v2 根因。复盘结论要么改代码、要么改回滚线,两者必居其一,不允许「再观察看看」。回滚复盘的口径与 k8s-pipeline-guide 里的发布事故模板一致,两边共用一套复盘语言。

灰度指标怎么读

观察期判定靠三组数字,缺一组都可能放过问题:

指标组 判定口径 典型陷阱
技术 v2 错误率不超 v1 的 1.5 倍且绝对值 < 3% 底噪抬升被误判为 v2 坏
性能 v2 P99 不超段预算 110% GC 抖动造成假越线
业务 下单成功率、支付回调量波动 ±2% 只看技术绿,业务已伤

业务指标是灰度独有的一层:技术全绿不代表业务没问题,一次 v2 改了对账口径,错误率 P99 都干净,但财务 T+1 对账多出 37 笔差异,二档观察期就该被业务指标拦下。业务指标进灰度看板是本篇坚持的一条:灰度不能只看技术面。

一次完整放量的时间线

以订单服务 2.4.1 到 2.4.2 为例:

时间 动作 结果
D-1 22:00 v2 发上去 0% 流量预热 缓存热,无异常
D 10:00 升一档 5% 观察 30 分钟全绿
D 10:40 升二档 20% 观察 2 小时,错误率持平
D 12:50 升三档 50% 隔夜观察
D+1 低谷 收口 100%,撤 v1 留 7 天 v1 日志快照

整个周期约 24h。期间一次二档观察的 P99 顶到预算 108%,没破 110% 线没触发回滚,人工评估为 GC 抖动,继续晋级——判定「自动回滚线」与「人工判断」的边界也要写进 SOP,不是所有越线都自动动。

放量记录全部留档:每档的比例、时长、三组指标快照、晋级或回滚决定,写进发版单存 COS(gov/release/{svc}/{ver}/)。季度复盘时用这批档案算「一次过收口率」——全程无回滚直接收口的发布占比。我们的基线是 82%,低于 70% 就触发发布质量专项,查是代码问题还是观察口径问题。

踩坑:三次灰度事故

第一,灰度桶不均匀。早期按请求时间随机分桶,大促期间流量在分钟级抖动,v2 实际占比 5% 时一度冲到 11%,观察期指标失真。改稳定 hash 后同一用户永远同桶,占比曲线平稳,观察才有效。

第二,v2 实例数太少。二档 20% 流量只压 1 个 v2 实例,P99 直接顶穿预算自动回滚,其实代码没问题,是容量没跟上。规则:每升一档前 v2 实例数按「档位比例乘全量实例数」补齐,扩容动作进放量流程,不靠人工记。

第三,回滚后取证链断了。一次回滚 40 分钟后 v2 实例被自动回收,想查根因时日志没了。收口动作加一条:回滚后的 v2 实例延迟 24h 再摘注册,日志与指标在 CLS 留全量 7 天,取证窗口写进流程。

第四,灰度期间的指标要分版本看。二档观察时把 v1 与 v2 的曲线叠在同一张图,错误率 v1 本身也有 0.8% 的底噪,v2 顶到 1.3% 时第一反应误判为「v2 坏了」,细看是 v1 的底噪在涨(一次数据库慢查询),v2 其实是干净的。规则:所有灰度观察指标按 version 维度拆开存,对照必须 v1/v2 分曲线,叠图只用于展示不用于判定。

灰度与数据变更的配合

代码灰度好做,数据变更是另一类:加字段、建索引、改枚举值。我们的配合规则:

数据变更类型 灰度配合 理由
加新字段 先双写,v2 读新字段 老版本可忽略未知字段
建大表索引 独立发布,不随灰度 建索引与版本无关
枚举加值 先全量布兼容读,再灰度写 旧版本必须认新值
删字段 永不随灰度,走 2.2 的 v2 关版 删字段是破坏性变更

枚举那次出过事:新枚举值先灰度写出,全量读兼容没布齐,旧版本服务读到新值解析失败,客诉 4 起。之后「读兼容全量前禁止写新值」写进了数据变更 checklist。

小结

灰度三档(5/20/50)加自动回滚,回滚靠拨比例 8 秒生效,v2 实例数随档位补齐,回滚后 24h 取证窗口。数据变更与代码灰度走同一张 checklist,读兼容全量前禁止写新值。灰度把发布风险从「全量赌博」压到「可控放量」,前提是 4.3 的看线能自动判定晋级与回滚,观察期按 version 维度分曲线。下一篇 4.2 把 TraceID 铺全链路,灰度观察才有线可对。

延伸阅读