零停机发布:把“发布”和“用户感知”解耦
网站更新最怕的不是代码写错,而是发布动作本身造成用户可见的中断。零停机发布的核心思路很简单:永远同时保留两个环境,新版本在完全验证前不承担全部流量。这样发布就从“一把梭”变成了“可进退的切换”。
三种常见发布方式怎么选
| 方式 | 原理 | 停机时间 | 回滚速度 | 适合场景 |
|---|---|---|---|---|
| 直接替换 | 停旧起新 | 有(秒~分钟级) | 慢 | 低流量内部工具 |
| 蓝绿发布 | 两套环境切换 | 基本为零 | 秒级切回 | 大多数线上服务 |
| 金丝雀发布 | 小流量逐步放量 | 为零 | 秒级回滚 | 高流量、稳定性要求高的服务 |
| 滚动更新 | 分批替换实例 | 短 | 中等 | 容器化、实例多的集群 |
中小团队最划算的起点是蓝绿发布:逻辑直观,回滚就是一次网关切流,不需要复杂的编排。
蓝绿发布流程:从预发布到全量
一套最小可用的蓝绿发布流程如下:
- 预发布验收:新版本先在预发布环境跑完功能验收,包括冒烟测试和关键链路回归。
- 部署绿色环境:把新版本部署到当前不承载流量的绿色环境,执行健康检查(启动探针、依赖连通性)。
- 小流量试切:网关把 10% 流量切到绿色环境,观察 10 分钟。这一步能暴露配置类错误——比如数据库迁移没生效、环境变量指向错误。
- 全量切流:小流量无异常后切到 100%。
- 保留蓝色环境:蓝色环境保留一段时间(建议至少一个发布周期),用于快速回滚。
健康检查不能只看“能访问”
很多人把健康检查做成 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