发布策略:蓝绿、灰度与回滚

发布是运维风险最集中的动作:代码、配置、数据库、缓存四类变更同时落地,用户流量还在持续进来。发布策略的选择本质上是在回答一个问题——出问题时,多久能回到已知可用的状态。本章给出四种策略的对照,并重点讲清蓝绿切换、权重灰度、数据库变更顺序与回滚触发条件。

要解决的问题

  • 停机发布、滚动、蓝绿、灰度该怎么选?
  • 蓝绿切换怎么做才能「瞬时完成、一键回滚」?
  • 新版本只想让 5% 的用户用到,怎么实现?
  • 数据库结构与代码的顺序搞错会怎样?
  • 发布前要准备什么,发布后要看多久?
  • 什么指标出现异常就必须立刻回滚?

四种策略对照

策略停机时间资源成本回滚速度适用场景
停机发布分钟级最低(单份资源)慢,需重新部署旧版本内部系统、有明确夜间窗口、用户量极小
滚动发布多一台的余量中,再滚回去要几分钟多实例服务、接口类应用
蓝绿发布双份资源秒级,切软链/换 upstream有明确两套环境、要求秒级回滚
灰度发布视实现秒级,权重归零用户量大、变更风险高、需要看业务指标

选择建议:个人站和中小项目,蓝绿是性价比最高的选择——两套目录、一个软链,实施成本很低,却能拿到「秒级回滚」这个最值钱的能力。滚动发布适合多实例后端,灰度适合用户量大、必须按比例验证的场景。

蓝绿发布实操

# 目录结构:blue 是当前线上,green 是待发布
# /srv/www/site-blue   当前版本
# /srv/www/site-green  新版本
# /srv/www/site  → 软链,Nginx 的 root 指向它

# 1) 部署新版本到 green(增量同步,不碰 blue)
rsync -avz --delete dist/ prod:/srv/www/site-green/

# 2) 灰度自测:用 --resolve 直连服务器但伪造 Host,验证的就是 green 吗?
#    Nginx root 指向软链,所以自测前先把软链指过去再验,或用一个测试端口指向 green
ssh prod 'curl -s -o /dev/null -w "%{http_code}\n" -H "Host: example.com" http://127.0.0.1/'

# 3) 切换:一条命令,瞬时完成
ssh prod 'ln -sfn /srv/www/site-green /srv/www/site && sudo nginx -t && sudo systemctl reload nginx'

# 4) 回滚:还是同一条命令,切回 blue
ssh prod 'ln -sfn /srv/www/site-blue /srv/www/site && sudo nginx -t && sudo systemctl reload nginx'

Nginx 侧配置只需要让 root 指向软链:root /srv/www/site;。切换软链后必须 reload,因为 Nginx 会缓存已打开文件的路径信息(open_file_cache)与已解析的根目录,不 reload 可能继续读到旧文件。蓝绿的三个前提:产物必须自包含(不依赖服务器上手工改过的文件)、两个版本的接口向后兼容(用户浏览器里可能还缓存着旧前端)、回滚包必须还在

Nginx 权重灰度

upstream app_cluster {
    server 10.0.0.11:8080 weight=95;   # 旧版本
    server 10.0.0.21:8080 weight=5;    # 新版本,先吃 5%
    keepalive 32;
}

放量节奏:5% → 25% → 50% → 100%,每档至少观察 15~30 分钟,并且每档都要看「错误率、P95 延迟、核心业务指标」三项。权重灰度的好处是回滚极快(把新实例权重改成 0 或直接标 down 并 reload);坏处是新旧版本必须能同时工作,这对数据库结构提出了硬要求。

如果按用户而不是按流量灰度,用 map 生成固定分组更合适:

# 用 cookie 或用户 ID 做稳定分流,同一用户始终落在同一版本
map $cookie_ab_group $backend_pool {
    default  pool_old;
    "new"    pool_new;
}
upstream pool_old { server 10.0.0.11:8080; }
upstream pool_new { server 10.0.0.21:8080; }
server { location / { proxy_pass http://$backend_pool; } }

注意 proxy_pass 用变量时会走不同的解析路径,此时 upstream 名要能被解析,且不能依赖 URI 替换规则。

数据库变更与代码发布的顺序

这是发布事故里破坏力最大的一类:代码已经删掉了对旧字段的引用,而数据库里还有实例在读它。正确做法是**「先兼容后上线,扩 → 改 → 删三步走」**:

步骤动作代码状态观察点
1 扩加新列(可空或有默认值)、加新表、加索引老代码完全不感知确认加列不锁表、索引不在高峰期建
2 双写新代码同时写新旧字段,但先读旧的新版本上线比对两条数据是否一致
3 切读新代码改为读新字段已在跑新版本观察一到两天,确认无缺数据
4 删确认无任何引用后,删旧列/旧表老版本已全部下线单独一次变更,绝不与代码发布同批

绝对不要做的三件事:发布时 DROPRENAME 老代码仍在使用的列;在没有双写的情况下直接切读;把「结构变更 + 代码发布 + 数据订正」放在同一个时间窗口里做——出了问题你都不知道该回滚哪一个。

缓存与前端资源版本

  • 前端产物用内容哈希命名(app.a1b2c3.js),HTML 用 no-cache,发布后用户拿到新 HTML 才会去请求新资源(见第 19 章)。
  • no-cache 只保证「会去问服务器」,用户不打开页面就不会更新,所以接口必须保持向后兼容至少一个版本:老前端 + 新后端要能正常工作。
  • 发布时清理应用层缓存要用带版本的方式,不要 FLUSHALL——全量清空会把数据库瞬间打穿。
  • CDN 只刷 HTML 与 index.html,静态资源靠文件名变更自然生效。

发布检查清单

检查项完成标准
备份发布前完成数据库与上传目录备份,并确认备份文件可读
回滚包上一版本的目录/镜像 tag 仍在,且验证过能启动
变更窗口避开业务高峰、大促、竞品活动期
监控就位错误率、P95 延迟、核心业务指标面板已打开
通知群里同步开始时间、发布内容、负责人
数据库结构变更已按扩/改/删拆分,本次只做兼容性变更
留观至少 15~30 分钟,期间不做其他配置变更

回滚触发条件

指标阈值动作
5xx 比例超过 1% 且持续 3 分钟立即回滚,不讨论
P95 延迟超过基线 2 倍且持续 5 分钟先看依赖是否异常,无明确外部原因就回滚
核心接口成功率低于 99%立即回滚
业务指标登录/下单量同比下跌 30%立即回滚并排查
日志异常量新出现的异常类型持续增长评估后回滚

回滚要写进流程,而不是临时决定:把「谁有权决定回滚」提前定好,避免出事时还在群里争论。回滚之后先保证用户可用,再慢慢查根因——顺序反了,用户会替你做出更差的选择。

常见坑

现象做法
软链切换后没 reload用户仍看到旧页面切换后 nginx -t && systemctl reload nginx
产物依赖服务器上的手工文件切到新目录后配置丢失、功能异常产物必须自包含,配置走 Git
灰度的新旧版本接口不兼容5% 用户报错,且回滚也救不了已写入的数据数据库变更按三步法,接口保持兼容
发布和结构变更同一批出问题不知道该回滚哪个拆成独立变更,中间留观察期
只做备份不验证真出事发现备份是空的或坏的每月做一次恢复演练(见第 27 章)
没有明确阈值一直在「再看看」中错过回滚窗口事先写好指标与阈值
回滚包被新发布覆盖想回滚发现旧版本已不存在每次部署到带版本号的独立目录

小结:发布策略的选择标准是「回滚速度」,蓝绿用两个目录加一个软链就能拿到秒级回滚,是中小项目性价比最高的方案;权重灰度用来按比例验证新版本,前提是新旧接口与数据完全兼容;数据库变更永远走「扩 → 双写 → 切读 → 删」四步,绝不与代码发布同批;发布前把备份、回滚包、监控、通知、留观五项准备好,并把回滚触发阈值写死在流程里——发布这件事,赢在不慌。

笔记加载中…