一个团队说"我们做了蓝绿部署",问下去发现两个环境共用一套数据库,切换时新版本要跑迁移脚本,回滚就意味着把数据改回去。这不是蓝绿,这是"两个应用实例轮流承担全部流量",风险与单实例切换差不多。
蓝绿与灰度经常被当成同义词,实际差别在一个问题上:切换的单位是什么。蓝绿切换的是"整套环境",灰度切换的是"一部分流量"。前者要求两套环境彼此独立,后者要求能按比例分流——两者的前置条件完全不同。
1. 两者的定义性差别
| 维度 | 蓝绿 | 灰度 |
|---|---|---|
| 切换单位 | 整套环境(全量) | 流量比例(部分) |
| 需要什么 | 两套完整环境 + 数据兼容 | 分流能力 + 指标对比 |
| 回滚方式 | 切回旧环境 | 把比例调回 0 |
| 暴露窗口 | 切换瞬间全量暴露 | 逐步扩大 |
| 主要风险 | 数据与状态不兼容 | 分流不均与指标误判 |
这个差别还会影响"谁能决定切换":蓝绿是一把手决策(一次到位,需要一个明确的执行时刻),灰度是持续判断(每次扩大比例都要看指标)。所以蓝绿的难点在准备,灰度的难点在观察。
第一项决定了一切:蓝绿是二元的(0 或 100%),灰度是连续的(0 到 100% 之间任意值)。二元切换的优点是干净、回滚快;缺点是切换那一刻所有风险同时兑现。
2. 蓝绿的前提条件
| 条件 | 不满足的后果 |
|---|---|
| 两套环境独立 | 实际是同一套,回滚困难 |
| 数据双向兼容 | 回滚要改数据 |
| 会话不粘在实例上 | 切换后用户被登出 |
| 外部依赖能同时服务两套 | 回调地址、白名单冲突 |
| 切换是原子操作 | 部分流量走新、部分走旧 |
第二项是最容易被忽略的:只要新版本写了旧版本读不懂的数据,蓝绿就退化成单向迁移。判断方法是问一句"切回去之后,旧版本能正常读这些数据吗"。
第四项常被当成小事,实际上它经常成为卡点:支付回调地址、第三方白名单、短信签名这些都是外部登记的,改一次要走对方的流程。两套并存意味着这些登记得同时有效。
第三项在登录系统上很常见:会话存在实例本地内存里,切换后所有用户重新登录。解法是会话外置,或接受"切换即登出"的代价并在低峰做。
// 蓝绿可行性四问
1. 两套环境是否完全独立(含数据库、缓存、密钥、域名)?
2. 新版本写入的数据,旧版本能否读取?(决定能否回滚)
3. 会话与本地状态是否外置?
4. 外部系统是否同时接受两套地址(回调、IP 白名单)?
全"是" → 真蓝绿;任一"否" → 只是双实例轮换
3. 灰度的前提条件
| 条件 | 不满足的后果 |
|---|---|
| 可按稳定维度分流 | 同一用户在两版间跳变 |
| 两版共享兼容数据 | 互相读不懂对方写入 |
| 有可对比的指标 | 无法判断该不该扩大 |
| 有明确的扩大/回退规则 | 靠感觉调比例 |
可以直接用一段判断把"稳定"这件事写清楚:
// 稳定维度的三个要求
1. 同一用户多次请求落在同一侧(否则行为不一致)
2. 维度可获取且不随环境变化(登录态、租户、地域都满足)
3. 侧别可追溯(日志里能看出这个请求走了哪一版)
// 反例:按请求 ID 哈希 → 同一用户在两版间跳变,问题被平均掉
第一项是灰度的核心:分流维度必须稳定。按用户 ID 哈希、按租户、按地域都稳定;按请求随机则不稳定——同一个用户这次走新版、下次走旧版,行为不一致会掩盖真实问题。
// 灰度分流:维度稳定 + 比例可调 + 白名单优先
canary:
dimension: "header[x-user-id]" # 稳定维度
buckets: 1000 # 分母,便于精细调整
percent: 50 # 千分之五十
allowlist: ["U-1001", "U-1002"] # 内部账号先走新版
metrics: # 判定依据,必须两版可比
- error_rate
- p99_latency
- business_conversion
4. 指标对比是灰度最难的部分
| 陷阱 | 表现 |
|---|---|
| 样本量不足 | 小比例下指标波动大,看不出差异 |
| 人群不同质 | 白名单用户与普通用户行为不同 |
| 指标口径不同 | 两版埋点位置变了,不可比 |
| 时间窗口错位 | 新旧版覆盖的业务高峰不同 |
第二项很实际:先让内部账号走新版,然后用这批人的数据判断新版是否正常,结论往往偏乐观——内部账号的使用路径最简单。
判定规则要事先写下来,而不是临场决定:
// 扩大与回退规则:写在配置里,避免"看感觉"
canary_rules:
expand_when:
- "error_rate < baseline * 1.2 持续 30m"
- "p99 <= baseline * 1.1 持续 30m"
rollback_when:
- "error_rate > baseline * 2 或出现新的错误类型"
- "任一 P1/P2 缺陷"
max_percent_per_step: 10 # 每步最多 10%,避免一次跳太多
min_observation: 30m # 每次扩大后至少观察 30 分钟
第四项在灰度跨天时最明显:新版只在白天承接 10% 流量,而旧版覆盖了全天,两者的延迟分布自然不同。
还有一个实际操作上的细节:蓝绿的切换要有"回切演练"。只演练过切到新版、没演练过切回旧版,等于只验证了一半。回切往往更麻烦,因为它要面对"新版已经写了一些数据"这个状态。
// 切换与回切都要计时并记录:两个方向分别多久
bin/switch.sh --to green && echo "switch-out $(date +%s)"
bin/verify.sh --expect green
// 回切演练(低峰执行)
bin/switch.sh --to blue && echo "switch-back $(date +%s)"
5. 组合用法:先灰度,再蓝绿
| 阶段 | 做法 | 目的 |
|---|---|---|
| 1 灰度 1% → 50% | 按稳定维度逐步放量 | 发现功能与性能问题 |
| 2 观察一个完整周期 | 覆盖高峰与低峰 | 排除时间因素 |
| 3 全量切到新环境(蓝绿) | 原子切换 | 完成收尾与资源回收 |
| 4 旧环境保留一段时间 | 可切回 | 兜底 |
这个组合把两种机制的优点串起来:灰度负责"发现问题",蓝绿负责"干净收尾"。只用灰度会有"永久两套并存"的问题,只用蓝绿则没有逐步观察的机会。
6. 排错:五种"切换出问题"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 回滚后数据错乱 | 新版写了旧版读不懂的数据 | 先做数据兼容或前向兼容写入 |
| 切换后用户被登出 | 会话存在实例本地 | 会话外置 |
| 灰度期间行为不一致 | 分流维度不稳定 | 换稳定维度 |
| 灰度看不出问题、全量出事 | 样本不足或人群不同质 | 拉长观察、覆盖高峰 |
| 切换后回调失败 | 外部系统只认旧地址 | 提前登记双地址 |
排错时有个快速判断:看有没有"只在新版存在"的错误类型。如果新错误类型的数量随流量比例同步增长,说明这是新版引入的确定性问题,而不是随机波动——这时应当立即回退,而不是继续观察。
第一个是最贵的:回滚发现要改数据,等于回滚失败。这类问题的唯一预防手段是切换前问"旧版本能读吗",并真的用旧版本读一遍。
7. 边界:什么情况不必区分
| 场景 | 为什么 | 做法 |
|---|---|---|
| 无状态服务且无数据变更 | 两者退化为同一件事 | 直接滚动发布 |
| 单实例部署 | 无"两套"可言 | 停机发布 |
| 内部工具 | 无用户影响 | 直接替换 |
| 已有服务网格分流能力 | 蓝绿只是灰度的一档 | 全部用灰度 |
还有一条与资源有关的边界:蓝绿要求两套环境同时在线,成本是双份。在小规模系统里这笔成本可能超过风险收益,此时"滚动发布 + 快速回滚"是更合理的选择——它不提供零风险的切换,但成本低得多。
判断口径一句话:这次发布会不会写入旧版本读不懂的数据。不会,灰度就够;会,就必须先解决数据兼容,否则蓝绿也救不了回滚。
8. 自测
- 蓝绿与灰度的定义性差别是什么?它如何决定了两种机制的风险形态?
- 蓝绿的五个前提条件中,哪一项缺失会让它退化成单向迁移?判断方法是什么?
- 灰度的分流维度为什么必须稳定?按请求随机分流会造成什么后果?
- 灰度指标对比的四个陷阱里,哪一个会让"内部账号先试"的结论失效?
- "先灰度再蓝绿"的组合各自负责什么?只用其中一种会留下什么问题?
- 判断"该用蓝绿还是灰度"的那个问题是什么?