企业级灰度发布技术方案
1. 方案目标
灰度发布不是简单地把新版本部署到几台服务器,而是让新版本先接收一小部分真实或仿真的请求,在可控范围内观察系统和业务指标,确认稳定后再逐步扩大流量。
对于国际支付系统,发布治理需要同时满足:
- 新旧版本可以在短时间内共存;
- 数据库结构变化不会让旧代码立即报错;
- 新版本出现问题时,可以快速停止放量并切回稳定版本;
- 已经产生订单、支付或消息后,能够通过幂等、对账和补偿使业务最终正确。
本文聚焦两种生产中常见的灰度落地方式:
- 生产共享数据库的应用灰度:新旧应用集群共享生产数据库,由网关控制部分流量进入新版本;
- 影子流量灰度:复制生产数据或请求到独立环境,新版本只执行计算和验证,不产生真实资金副作用。
蓝绿发布是另一种重要的发布组织方式,也可以和灰度发布组合使用。
2. 先把几个概念分清楚
2.1 灰度发布
灰度发布是逐步扩大新版本流量的方法:
旧版本 v1:100% 新版本 v2:0% 旧版本 v1:99% 新版本 v2:1% 旧版本 v1:95% 新版本 v2:5% 旧版本 v1:80% 新版本 v2:20% 旧版本 v1:0% 新版本 v2:100%每一步都观察一段时间。如果异常指标超过阈值,就停止扩大流量,必要时把流量切回旧版本。
灰度比例不一定按照百分比,也可以按照用户、商户、国家、币种、支付渠道或设备类型切分。
2.2 金丝雀发布
金丝雀发布是灰度发布的一种具体方式。新版本先只接收极少量流量,用小范围流量验证版本是否安全。
金丝雀发布强调:小流量、强观察、快止损。
2.3 蓝绿发布
蓝绿发布同时准备两套完整应用环境:
蓝环境:稳定版本 v1,承接全部流量 绿环境:新版本 v2,完成部署和验证验证完成后,直接把流量从蓝环境切到绿环境:
如果绿环境出现问题,再把流量切回蓝环境。
蓝绿发布的优势是切换清晰、回退快;缺点是需要同时准备两套资源,而且数据库仍然需要考虑新旧版本兼容。蓝绿发布不等于灰度发布,但可以组合使用:先让绿环境接收小流量,确认稳定后再全量切换。
2.4 三者的关系
灰度发布:逐步放量的总方法 金丝雀发布:先用极少量流量探测新版本 蓝绿发布:准备两套完整环境,通过切换入口流量完成切换3. 总体架构
生产环境中的灰度发布,通常不是研发人员直接修改 Nginx 配置,而是通过发布平台配置版本、流量比例和灰度规则。发布平台再调用网关、Ingress、服务网格、云负载均衡或公司内部流量系统完成切流。
统一流量入口的实现可能是:
- Nginx 或 Ingress:基础反向代理和权重切流;
- Spring Cloud Gateway:基于用户、商户、Header、市场等业务条件路由;
- Envoy 或 Istio:服务网格中的动态流量治理;
- 云负载均衡:基础流量分配;
- 公司内部流量平台:对上述组件做统一封装。
因此,更准确的说法是:
发布平台控制统一的流量路由层,底层组件可以是 Nginx、Spring Cloud Gateway、服务网格或云负载均衡。
4. Kubernetes 中的三Pod金丝雀
假设当前服务有三个Pod:
Pod-A:稳定版本 v1 Pod-B:稳定版本 v1 Pod-C:新版本 v2如果三个Pod被放进同一个Kubernetes Service,Service会把请求分发到可用Endpoint。实际流量通常只是接近三分之一,并不保证严格的 1/3,也不能精确控制某个用户始终进入同一版本。
原因包括:
- 负载均衡可能以连接或请求为单位;
- 长连接会造成流量不均;
- 不同请求耗时不同;
- Pod的处理能力可能不同;
- Kubernetes Service本身不提供完整的业务灰度规则。
企业级系统通常把稳定版本和灰度版本拆成两个Service:
| Service | 版本 |
|---|---|
| stable-service | v1 Pod |
| canary-service | v2 Pod |
再由网关或服务网格控制比例:
canary-service:1% stable-service:99%如果需要按商户、市场或用户切分,则由路由层执行规则:
| 灰度条件 | 路由版本 |
|---|---|
| 内部测试商户 | v2 |
| 新加坡市场 | v2 |
| 其他市场 | v1 |
结论是:一个Service下两个旧Pod加一个新Pod,适合做粗粒度金丝雀,但不能认为默认严格是1/3。精确灰度要使用独立Service和网关权重或业务路由规则。
5. 灰度流量如何切分
5.1 按比例切分
稳定版本:99% 灰度版本:1%适合验证整体系统性能,但不能保证灰度版本覆盖所有业务场景。
5.2 按用户切分
| 路由条件 | 路由版本 |
|---|---|
| hash(user_id) % 100 < 5 | 灰度版本 |
| 其他用户 | 稳定版本 |
同一个用户应保持稳定路由,避免在两个版本之间来回切换。
5.3 按商户切分
国际支付中通常更有价值:
| 灰度条件 | 路由版本 |
|---|---|
| 内部测试商户 | 灰度版本 |
| 低风险商户 | 灰度版本 |
| 其他商户 | 稳定版本 |
5.4 按市场、币种和渠道切分
| 市场、币种和渠道条件 | 路由版本 |
|---|---|
| 新加坡 + SGD + 渠道A | 灰度版本 |
| 其他市场和渠道 | 稳定版本 |
不同国家、币种、渠道和结算规则的业务风险不同,因此国际支付不应只按百分比切流。
6. 数据库治理:表结构必须向后兼容
灰度发布的重要前提是:数据库表结构必须同时支持旧代码和新代码。
生产稳定代码 v1 生产灰度代码 v2 ↓ 同一套生产MySQL6.1 允许的结构变更
可以允许向后兼容的结构扩展,例如:
ALTERTABLEpayment_orderADDCOLUMNchannel_codeVARCHAR(32)NULL;旧代码不认识 channel_code,但只要这个字段允许为空,旧代码仍然可以正常读写原有字段。
同类型字段的安全扩张通常可以允许,例如:
ALTERTABLEmerchantMODIFYCOLUMNmerchant_nameVARCHAR(200);但即使是长度扩张,也要评估表规模、锁表时间、数据库版本和是否使用在线DDL工具,不能把所有 ALTER 都视为无风险。
6.2 禁止的危险变更
发布流程中禁止或严格禁止:
DROPCOLUMNDROPTABLETRUNCATETABLEALTERCOLUMNTYPE同时禁止:
- 直接修改字段语义;
- 直接把可空字段改为非空字段;
- 新增会阻断旧代码写入的约束;
- 重命名旧字段后立即删除旧字段;
- 将代码发布和删除字段绑定在同一个发布动作中。
6.3 数据库变更顺序
以新增 channel_code 为例:
数据库先做兼容性扩展,应用再发布,最后才考虑清理旧结构。
6.4 发布流程禁止DML,但业务运行时仍然会产生DML
这里必须区分两件事。
发布脚本中的DML,例如:
UPDATEpayment_orderSETstatus='PROCESSING'WHERE...;可以禁止直接执行。所有数据修复和迁移操作必须通过数据变更平台完成,平台应提供:
- SQL审批;
- 权限控制;
- 影响行数预估;
- 执行前预览;
- 分批执行;
- 操作审计;
- 失败暂停;
- 数据校验;
- 结果留痕。
但是,业务运行时的 INSERT、UPDATE 和 DELETE 不可能被禁止,因为创建订单、更新支付状态和写入资金流水本身就是业务DML。业务DML需要通过事务、状态机、幂等和权限来保证正确性。
更准确的治理规则是:
禁止发布脚本和迁移脚本未经平台审批直接执行DML;不禁止业务服务在正常业务流程中执行受控DML。
7. 两种主流生产灰度方案
7.1 方案一:共享生产数据库的应用灰度
用户请求 ↓ 网关按规则切分流量 ├── 稳定应用 v1 └── 灰度应用 v2 ↓ 同一套生产MySQL特点:
- 新旧应用共享生产数据库;
- 写请求进入生产主库;
- 读请求根据读写策略访问主库或只读副本;
- 灰度版本产生的是真实业务数据;
- 必须保证数据库向后兼容;
- 支付、退款和结算操作必须有幂等保护。
适合验证真实业务链路,但风险较高。国际支付中通常先选择内部商户、低风险市场或低比例流量。
7.2 方案二:影子流量灰度
真实生产请求 ├── 稳定版本:真正执行并产生业务结果 └── 灰度版本:复制请求,只做计算和对比 ↓ 独立灰度数据库影子流量通常需要:
- 发版前复制一份生产数据,或通过快照和增量同步保持数据接近实时;
- 将生产请求复制给灰度版本;
- 灰度版本执行计算、查询和规则判断;
- 把灰度结果与稳定版本结果进行比较;
- 禁止灰度版本产生真实扣款、退款、记账或外部消息副作用。
数据对比不能简单做整库字节比较,因为时间字段、流水号和执行顺序可能不同。应该比较业务结果和不变量:
订单状态是否一致 应付金额是否一致 手续费计算是否一致 路由渠道是否符合规则 清分总额是否满足平衡公式 是否出现额外扣款或重复记账影子流量安全性高,但实现成本更高,需要处理生产数据脱敏、请求复制、外部依赖模拟、数据同步和结果差异分析。
7.3 两种方案如何选择
| 目标 | 推荐方案 |
|---|---|
| 验证真实生产链路和真实数据库压力 | 共享生产数据库的应用灰度 |
| 验证新旧逻辑结果,不允许产生真实副作用 | 影子流量灰度 |
| 支付扣款、退款和资金记账 | 优先影子流量或内部商户灰度 |
本文聚焦这两种主流落地模式。实际系统也可能配合独立预发布环境、蓝绿集群、流量镜像和区域单元化等手段。
8. MySQL主从和灰度发布不是一回事
这是面试中很容易混淆的两个概念。
8.1 MySQL主从解决什么问题
MySQL主从主要解决:
- 数据复制;
- 主库故障切换;
- 读请求扩展;
- 数据备份。
它不负责决定请求进入 v1 还是 v2,也不负责灰度比例和用户分流。
8.2 主从会产生读延迟
主库写入成功后,从库可能还没有复制完成:
主库:订单状态 = SUCCESS 从库:订单状态 = PAYING支付、订单和资金链路通常需要:
- 写后读主库;
- 同一个请求链路固定读主库;
- 根据复制位点等待从库追上;
- 对非关键查询接受最终一致。
8.3 灰度和主从的关系
灰度发布:决定应用流量进入哪个版本 MySQL主从:决定数据库如何复制和承载读写生产灰度可能是:
稳定应用 v1 ─┐ ├── 共享MySQL主从集群 灰度应用 v2 ─┘灰度应用写入生产数据时,仍然要写主库;不能因为是灰度版本就默认写从库。主从无法替代灰度隔离,也无法替代业务幂等和回滚机制。
9. 标准发布时序
下面以共享生产数据库、Kubernetes、一个灰度Pod为例。
正常放量
异常止损
上图同时展示正常放量和异常止损两条路径:正常时逐步扩大流量;异常时停止放量、切回 v1,并对已经产生的订单、支付和消息进行查询、对账与补偿。
切回流量只保证后续请求进入旧版本,不能自动撤销灰度版本已经产生的业务事实。
10. 发布门禁和自动止损
10.1 发布前门禁
数据库脚本门禁重点检查:
- 禁止 DROP COLUMN;
- 禁止 DROP TABLE;
- 禁止修改字段类型;
- 禁止未经平台审批执行DML;
- VARCHAR扩容需要评估锁表和在线DDL能力;
- 新增字段必须允许旧代码继续运行;
- 数据变更必须有影响范围和校验方案。
10.2 灰度中门禁
技术指标:
- 5xx错误率;
- P95和P99延迟;
- 超时率;
- CPU和内存;
- 数据库连接数;
- Kafka消息堆积;
- Redis错误率。
支付业务指标:
- 支付成功率;
- 支付超时率;
- 回调成功率;
- 重复支付数量;
- 订单状态不一致数量;
- 退款成功率;
- 对账差异数量。
示例规则:
| 触发条件 | 止损动作 |
|---|---|
| 错误率连续5分钟超过基线 | 停止扩大灰度 |
| P99延迟超过基线50% | 自动暂停 |
| 支付成功率下降超过阈值 | 切回稳定版本 |
| 出现重复扣款或资金差错 | 立即停止发布并人工介入 |
11. 关键工程边界
11.1 不把发布脚本当成数据修复工具
发布脚本只负责兼容性结构变更,不负责批量修改业务数据。所有数据修复和迁移操作通过数据变更平台执行,并且必须可审批、可审计、可分批、可暂停和可校验。
11.2 不把数据库主从当成灰度隔离
主从是数据库架构,灰度是应用流量架构。灰度应用是否进入生产主库,必须根据业务风险和灰度方案明确设计。
11.3 不把三个Pod的自然分流当成精确灰度
一个Service下两个旧Pod加一个新Pod,流量可能大致接近 2:1,但这不是严格的 1/3 灰度,也不能完成按商户或市场分流。精确灰度要使用独立Service和网关权重或业务路由规则。
11.4 不把影子流量当成真实支付
影子流量只能验证逻辑和结果,必须隔离扣款、退款、记账、消息发送和外部渠道调用等副作用。
13. 参考资料
- Amazon Web Services:什么是灰度发布
AWS文章强调的核心思路是:新版本逐步暴露给用户,持续观察系统和业务指标,根据结果扩大流量或回滚。本文结合国际支付场景补充了数据库向后兼容、影子流量、资金副作用隔离、MySQL主从和Kubernetes Pod级别发布等工程细节。