灰度发布的概念我很早就知道,但一直没机会实践。过往项目要么规模不够,要么节奏太紧。直到这个项目条件齐了,终于能把它落地。这篇笔记记录这次实战的过程。
一、功能背景
在微服务架构中实现基于 IP 白名单的灰度发布:让指定 IP 的请求进入新版(灰度)实例,其余流量留在稳定版实例,小范围验证后再全量放量。灰度标记贯穿”网关 → HTTP 服务 → Dubbo RPC”全链路。
二、技术栈与依赖版本
| 依赖 | 版本 | 说明 |
|---|---|---|
| JDK | 1.8 | 项目编译/运行版本 |
| Spring Boot | 2.7.18 | 基础框架 |
| Spring Cloud | 2021.0.8 | 微服务框架(Gateway 3.1.x、LoadBalancer 3.1.x) |
| Spring Cloud Alibaba | 2021.0.5.0 | Nacos 注册/配置、Sentinel |
| Dubbo(dubbo-spring-boot-starter) | 3.2.1 | 服务间 RPC(Apache Dubbo) |
| Nacos | 由 SCA 2021.0.5.0 管理 | 注册中心 / 配置中心 |
| Spring Cloud Gateway | starter 引入,版本由 2021.0.8 管控 | 网关 |
网关
pom.xml关键依赖:spring-cloud-starter-gateway、spring-cloud-starter-alibaba-nacos-discovery、spring-cloud-starter-alibaba-nacos-config、spring-cloud-loadbalancer(注意排除了spring-webmvc,网关是 WebFlux 环境)。
common-core 关键依赖:dubbo-spring-boot-starter、spring-cloud-starter-loadbalancer、javax.servlet-api。
三、整体架构
1 | 客户端请求 |
四、核心实现与关键代码
1. 网关层:流量染色(GrayStainGlobalFilter)
全局过滤器,order = -50。获取真实 IP(优先取 X-Forwarded-For 首值),命中白名单即判定灰度,向请求写入 X-Gray-Ip、X-Gray-Flag 头透传下游。
1 |
|
2. 灰度配置属性(GrayConfigProperties)
1 |
|
3. 网关负载均衡(GrayReactorLoadBalancer)
从 RequestDataContext 读请求头 X-Gray-Flag:灰度流量只选元数据 gray=true 实例,普通流量排除灰度实例;无匹配时降级全部实例,避免 503。
1 |
|
注册方式(GrayLoadBalancerConfig)—— 注意把 ObjectProvider 包装为 Supplier:
1 |
|
4. 通用路由与路径解析(ParseServiceGatewayFilterFactory)
配合单条路由 Path={serviceId}/**,动态构造 lb://{serviceId} 转发目标,无需为每个服务配置路由。
1 |
|
5. 服务层:HTTP 入口标记透传(GrayFlagFilter)
Servlet 过滤器,order = 0,在 AdornRequestBodyFilter(1)、AuthFilter(2) 之前执行。读到 X-Gray-Flag: true 时写入 Dubbo RpcContext,finally 清理防线程池复用泄漏。
1 | public class GrayFlagFilter implements Filter { |
注册(FilterRegisterConfig):
1 |
|
6. Dubbo 灰度透传双 Filter(consumer / provider)
consumer 端(GrayDubboConsumerFilter,@Activate(group = "consumer") 自动激活):
1 | //注意:Web环境,仅在HTTP入口接收请求后发起dubbo调用生效 |
provider 端(GrayProviderFilter,@Activate(group = "provider"),支持 A→B→C 嵌套链路):
1 |
|
7. Dubbo 灰度负载均衡(GrayDubboLoadBalance)
灰度调用只选 provider URL 参数 gray=true 的 invoker,普通调用排除,无候选时回退全部。
1 |
|
8. Dubbo SPI 扩展点注册
META-INF/dubbo/org.apache.dubbo.rpc.Filter:
1 | grayConsumerFilter=com.rh.joylife.filter.GrayDubboConsumerFilter |
META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance:
1 | gray=com.rh.joylife.loadbalance.GrayDubboLoadBalance |
9. 实例标记配置(测试配置,通过启动脚本 3 个 JVM 参数控制)
说明:gray 标记不写死在配置文件,bootstrap.yml 中改为 3 个独立占位符,由启动脚本 JVM 参数控制。3 个参数必须同时传对,否则链路会错乱:
METADATA_GRAY:控制 Nacos 注册元数据gray—— 网关GrayReactorLoadBalancer按此筛选转发目标(HTTP 层)PROVIDER_GRAY:控制 Dubbo provider URL 参数gray——GrayDubboLoadBalance按此筛选调用目标(RPC 层)CONSUMER_LB:控制消费方 Dubbo 负载均衡策略 —— 必须为gray,否则不走GrayDubboLoadBalance,灰度标记完全失效
为什么缺一不可:若
METADATA_GRAY=true而PROVIDER_GRAY=false,网关会把灰度流量转发给该实例,但该实例的 Dubbo URL 上没有gray=true,下游服务灰度调用时选不到它(或兜底混调),灰白链路错乱;若CONSUMER_LB不是gray,消费方根本不会走灰度负载均衡,灰度分流直接失效。
bootstrap.yml(默认值即稳定实例语义,灰度实例由脚本传参开启):
1 | spring: |
启动脚本示例(灰度实例 vs 稳定实例):
1 | # 灰度实例:3 个参数同时传 |
10. 网关侧配置(测试配置,gateway.gray 放 Nacos 动态下发)
说明:bootstrap.yml 中只保留通用路由等基础配置;gateway.gray.enable、gateway.gray.ip-whitelist 从本地文件移除,改放到 Nacos 配置中心(data-id:joylife-gateway.yaml,由 spring.cloud.nacos.config 拉取)。配合 GrayConfigProperties 上的 @RefreshScope,白名单增删后自动动态生效,无需重启网关——放量/收回灰度 IP 直接改 Nacos 即可。
bootstrap.yml(仅保留路由基础配置):
1 | spring: |
Nacos 配置(joylife-gateway.yaml):
1 | gateway: |
网关启动类需开启配置扫描:@ConfigurationPropertiesScan。
五、灰度发布操作流程
- 发布前:新版本实例启动脚本传
-DMETADATA_GRAY=true -DPROVIDER_GRAY=true(无需改配置重新打包),消费方CONSUMER_LB=gray默认开启 - 放量:新实例注册到 Nacos;在 Nacos
joylife-gateway.yaml中把测试人员 IP 加入gateway.gray.ip-whitelist,动态生效无需重启网关 - 验证:白名单 IP 走灰度实例,普通用户走稳定实例,流量隔离互不影响
- 全量:验证通过后,灰度实例去掉
-D...GRAY=true参数重启(或全量替换),灰度流量自动收敛到稳定版
六、关键技术点与注意事项
| 点 | 说明 |
|---|---|
| 灰度判定时机 | 网关在最外层(order=-50)完成判定并染色,保证下游统一读 header |
| 线程安全 | Servlet 层 finally 清理 attachment,Dubbo 层 filter 负责传递,防止 ThreadLocal 泄漏 |
| 嵌套链路 | provider filter 保证 A→B→C 多级 RPC 调用灰度标记不丢失 |
| 兜底设计 | 网关 LB 和 Dubbo LB 都有”无匹配→降级全部”逻辑,避免灰度实例未就绪时 503 |
| 双通道一致 | Spring Cloud LoadBalancer(HTTP 网关)和 Dubbo LoadBalance(RPC)两套选择逻辑各自实现,需同步维护 |
| 三参数联动 | 实例侧 METADATA_GRAY / PROVIDER_GRAY / CONSUMER_LB 必须一致,防止网关与 Dubbo 灰度判定错位 |
| 动态路由 | 通用路由 + 路径解析,StripPrefix=2 剥掉 /{serviceId} 前缀后再转发 |
| 版本坑点 | SCG 3.1.x 必须实现 Ordered 接口,@Order 注解会被 FilteringWebHandler 忽略 |
| 诊断手段 | GrayDubboLoadBalance static 块 + 每次选择打日志;DebugGatewayFilter 打印转发目标 URI |
七、涉及文件清单
| 模块 | 文件 | 作用 |
|---|---|---|
| gateway | GrayStainGlobalFilter |
IP 判定 + 流量染色(全局过滤器) |
| gateway | GrayConfigProperties |
灰度配置属性(支持动态刷新) |
| gateway | GrayLoadBalancerConfig |
注册自定义网关负载均衡器 |
| gateway | GrayReactorLoadBalancer |
网关实例筛选负载均衡 |
| gateway | ParseServiceGatewayFilterFactory |
通用路由 serviceId 解析 |
| gateway | DebugGatewayFilter |
转发目标调试日志 |
| gateway | JoyLifeGatewayApplication |
开启 @ConfigurationPropertiesScan |
| gateway | bootstrap.yml |
通用路由基础配置 |
| common-core | GrayFlagFilter |
HTTP 入口标记写入 RpcContext |
| common-core | GrayDubboConsumerFilter |
consumer 端标记透传 |
| common-core | GrayProviderFilter |
provider 端嵌套链路透传 |
| common-core | GrayDubboLoadBalance |
Dubbo 灰度负载均衡 |
| common-core | FilterRegisterConfig |
Servlet 过滤器注册 |
| common-core | META-INF/dubbo/* |
Dubbo SPI 扩展点注册 |
| App-openapi / App-user | bootstrap.yml |
灰度标记占位符 + 消费端负载均衡策略 |
(注:内容由 AI 辅助编写)
[越努力,越幸运!]