零停机发布:把“发布”和“用户感知”解耦

网站更新最怕的不是代码写错,而是发布动作本身造成用户可见的中断。零停机发布的核心思路很简单:永远同时保留两个环境,新版本在完全验证前不承担全部流量。这样发布就从“一把梭”变成了“可进退的切换”。

三种常见发布方式怎么选

方式 原理 停机时间 回滚速度 适合场景
直接替换 停旧起新 有(秒~分钟级) 低流量内部工具
蓝绿发布 两套环境切换 基本为零 秒级切回 大多数线上服务
金丝雀发布 小流量逐步放量 为零 秒级回滚 高流量、稳定性要求高的服务
滚动更新 分批替换实例 中等 容器化、实例多的集群

中小团队最划算的起点是蓝绿发布:逻辑直观,回滚就是一次网关切流,不需要复杂的编排。

蓝绿发布流程:从预发布到全量

一套最小可用的蓝绿发布流程如下:

  1. 预发布验收:新版本先在预发布环境跑完功能验收,包括冒烟测试和关键链路回归。
  2. 部署绿色环境:把新版本部署到当前不承载流量的绿色环境,执行健康检查(启动探针、依赖连通性)。
  3. 小流量试切:网关把 10% 流量切到绿色环境,观察 10 分钟。这一步能暴露配置类错误——比如数据库迁移没生效、环境变量指向错误。
  4. 全量切流:小流量无异常后切到 100%。
  5. 保留蓝色环境:蓝色环境保留一段时间(建议至少一个发布周期),用于快速回滚。

健康检查不能只看“能访问”

很多人把健康检查做成 curl /healthz 返回 200 就通过。但这个探针只能证明“进程活着”。更接近真实的检查应该覆盖:

  • 依赖可用性:数据库、缓存、消息队列都能连通;
  • 关键接口自检:/healthz 内部再探测一次首页渲染和一条写路径;
  • 日志无新增 ERROR:部署后 5 分钟内的错误日志数量在基线范围内。

回滚触发条件:提前写清楚,别现场开会

回滚条件应该像防火墙规则一样提前定死,而不是出了事再开会讨论。常见的触发线:

  • 5xx 错误率超过 1%(持续 2 分钟);
  • 95 分位延迟超过基线 50%;
  • 关键交易链路失败率异常(比如下单成功率下降超过 0.5 个百分点);
  • 内存或 CPU 持续高位且趋势向上。

一旦命中其中任意一条,第一步永远是切回蓝色环境,而不是在绿色环境里现场调试。线上现场排障是大忌,先恢复再分析。

一个真实场景:凌晨 2 点的发布

电商团队常在凌晨做发布,因为流量最低。有一次新版本改动了订单表结构,预发布环境测过没问题,但线上数据量是预发布的 30 倍,ALTER TABLE 卡住了半小时。因为采用的是蓝绿发布,网关立刻切回蓝色环境,用户毫无感知;线上问题在绿色环境里慢慢处理,第二天再用金丝雀方式分步放量验证。

这个案例说明:数据库变更最好单独规划,配合在线 DDL 工具(gh-ost、pt-online-schema-change)做在线变更,避免阻塞式 DDL 成为发布链路上的定时炸弹。

发布前检查清单

发布不是点击“部署”按钮就结束,建议把它做成一张可以反复用的清单:

  • 数据库迁移脚本在预发布环境完整跑过一遍,且可回滚
  • 新旧版本的配置项(环境变量、密钥、域名)都已核对
  • 健康检查接口返回真实状态,而不是硬编码 200
  • 监控告警能区分蓝色/绿色环境的指标
  • 回滚条件写进发布单,负责人明确
  • 发布窗口选在流量低峰,避开促销活动

常见问题

数据库结构变更怎么配合蓝绿? 原则是“向前兼容”:先让新旧两个版本都能读写同一份数据库。新增字段用可空列或默认值,删除字段至少晚一个发布周期。必要时用在线 DDL 工具。

缓存不一致怎么办? 切流后新旧版本的缓存 key 可能混用。建议发布时统一刷新缓存,或让缓存 key 带上版本号。

小团队没有网关怎么办? 没有 Nginx/网关也能做蓝绿:用两个不同端口或子目录部署新旧版本,通过 Nginx upstream 权重切流(比如 9:1 起步)。Nginx 自带的 weight 参数就能实现灰度。

参考:GitHub Actions 蓝绿部署示例 https://docs.github.com/actions/ ,Nginx upstream 模块文档 http://nginx.org/en/docs/http/ngx_http_upstream_module.html