发布本身不是难点,难点是新版本有问题时能在多大范围快速止损。滚动发布逐个替换实例,回滚要数十分钟,这段时间全部用户都在受影响面。蓝绿和灰度解决的都是这件事:把”回滚”换成”切流”,把止损时间从重建实例的量级压缩到改一次路由配置的量级。
蓝绿与灰度的差异
蓝绿部署维护两套完整环境。当前流量全走绿环境,新版本部署到蓝环境完成自检验证,通过后一次性从绿切到蓝,原绿环境保留一段时间作为回滚目标——回退就是再切回去。
优点直接:切换瞬间完成,回滚也瞬间,不存在”部分实例是新版本”的中间状态。缺点同样直接:要一倍资源冗余,含数据库在内的整套链路要么共享要么同步,成本实打实。
灰度发布(金丝雀)不是一刀切,而是让新版本先承接一小部分流量,观察指标后逐步放量到 100%。资源占用接近常态,能做到渐进验证,代价是放量周期长,且存在新旧版本并行的窗口,这段时间内两套代码都在处理真实请求。
| 维度 | 蓝绿 | 灰度 |
|---|---|---|
| 资源占用 | 双倍 | 接近常态 |
| 切换速度 | 一次性全切 | 分阶段放量 |
| 回滚 | 切回另一套环境 | 把权重调回 0 |
| 适用 | 版本差异大、强一致要求 | 常规迭代、需要观察用户行为 |
选择依据是变化的风险和资源的余量。基础设施层的重大变更(数据库版本、中间件升级)倾向于蓝绿;日常业务迭代倾向于灰度。实际中也常见组合:先用灰度在小范围验证,确认无误后再一次性切全量,此时的”切全量”在效果上等同于蓝绿。
流量切分的依据
放量的第一步是把哪些请求路由到新版本。常见的四种依据各有适用场景:
按权重。最简单,随机把 5% 请求打到新版本。适合验证性能与稳定性,不适合有状态体验——同一用户两次请求落到不同版本,表现可能不一致。
按用户 ID 哈希。对用户标识取哈希后按数值区间分配,同一用户稳定命中同一版本。代价是新版本必须兼容旧版本的数据。
# 对用户标识做哈希,稳定命中同一版本
split_clients "${cookie_uid}${remote_addr}" $canary {
5% "canary";
* "stable";
}
实际参数化时通常用整数区间而不是字符串前缀,方便在不改文法的情况下调整比例:
hash(user_id) % 100 < 5 -> 灰度组(可调 5、10、20、50、100)
按 Header 或 Cookie。测试人员带特定标记就走新版本,适合内部验证,不适合面向真实用户放量。
按地域或机房。先在单个可用区放量,出问题影响面可控。取舍在于故障隔离与样本代表性:单一区域的流量特征未必代表全局。
常被忽略的一点是分流要稳定。用户一旦进入灰度组就该持续待在灰度组,否则在两版本间来回切换,遇到数据兼容问题时现象难以复现。
新旧版本共存期的数据兼容
这是灰度发布最大的坑:新旧版本同时读写同一份数据,而它们的假设可能不同。
写兼容。新版本写了一个新字段,旧版本读到这条记录会忽略该字段——通常没问题。但如果旧版本读取后又整体写回(常见于”读-改-写”的更新逻辑),新字段就会被覆盖丢失。
读兼容。新版本依赖新字段才能渲染,而记录是旧版本写入的、没有该字段。轻则显示异常,重则空指针。稳妥做法是读取时给默认值兜底,并把”期望有值却为空”做成一条告警项。
唯一性与幂等冲突。新旧版本对同一笔业务用了不同的幂等键,重复扣款风险就出现了。幂等键的设计必须跨版本保持一致。
处理手段通常三条:先加后用——先上只写不读的版本,等数据铺开后再上读取该字段的版本;双写过渡——新旧字段同时写入,读旧写新,验证无误后切读;禁止删除——灰度期间不删字段不改语义,因为旧版本可能还在用。
数据库 schema 变更的向后兼容
代码可以秒级回滚,schema 变更不行。ALTER TABLE 一旦执行,回滚需要再一次变更。因此 schema 变更遵循三条原则,并且每个变更都被拆成多次发布:
先加后用。第一步只加列(可空,带默认值),不写不读;第二步开始双写;第三步切读;第四步清理旧列。每一步都是一次独立发布,可以单独回滚。加列的代价取决于具体实现和数据量,某些操作在大数据量表上仍然是重量级的,需要在低峰期执行并观察复制延迟。
不删不改。不要在同一个发布里删除列或改变列语义。删除至少延后到所有旧版本实例都已下线之后——通常是数周,因为可能还有缓存副本、离线任务或回滚计划依赖它。改语义比删更危险,因为它是静默的:列还在,含义变了,旧代码读到的值不再符合预期。正确做法是新增一列、双写、迁移、切读,旧列保留一段时间。
双写过渡。写入同时落到新旧两处,读取仍走旧路径,用离线任务比对新旧数据一致性,比对通过后才切换读路径,最后下线旧写入逻辑。这条原则在索引重建、分库分表迁移、字段类型调整中同样适用。
一个具体的迁移顺序示例:
发布 v1:新增 column new_col,代码仍读写 old_col
后台任务:回填历史数据,并持续校验 new_col 与 old_col 是否一致
发布 v2:代码双写 old_col + new_col,读仍走 old_col
校验通过:发布 v3,读切换至 new_col
观察一两个发布周期:发布 v4,停止写 old_col(schema 暂保留)
延后清理:确认无回滚需求后再 DROP old_col
每一步都能独立回滚,唯一不可逆的是最后的 DROP,这也是它必须排到最后的原因。
回滚窗口与自动化判断
灰度放量必须配自动化判断,否则”观察一小时”就退化成盯看板的主观判断。指标通常三类:
技术指标:错误率、P99 延迟、CPU 与内存、GC 频率。要对比灰度组与基准组的相对值而非绝对值——大盘上涨可能只是流量自然增长。
业务指标:下单成功率、支付转化率。技术指标正常不代表业务正常,例如新版本没报错但少了一步校验,成功率仍会下滑。
资源指标:队列积压、连接池占用。这类指标常常先于错误率恶化——资源耗尽前的排队阶段,请求仍能成功,只是慢。
自动化的实现通常是:放量后进入观察窗口,窗口内持续对比两组指标,任一指标劣化超过阈值即自动回滚。关键是阈值要有统计意义——样本量很小时单个失败就能让错误率看起来很高,所以要设最低样本量门槛(积累若干次请求或若干分钟)再引入置信判断。
回滚路径必须预先验证。灰度权重从 20% 调回 0 在配置上是一行改动,但如果应用缓存了路由决策、或者有非幂等的长任务在执行,”调回 0″不等于立即止损。因此还需要一个更粗暴但可靠的整体功能开关,它能在不重新部署的情况下禁用新代码路径。
回滚窗口的长度取决于指标收敛速度。太短看不到低频问题(只在特定数据组合下触发的异常),太长则延长风险暴露。经验上是先短窗口高频观察,放行后再保留一段整体观察期,而不是一次性把窗口拉长。
评论(0)
还没有评论,来抢沙发。
请 登录 后参与评论。