上线别再“一刀切”!Gateway 做流量染色 + 灰度发布,告别线上事故
大家好,我是小富~
最近团队迭代频繁,连续几周都在做新功能上线,从测试环境验证到生产环境放量,全程谨小慎微没出一次故障,主要是用好了 Spring Cloud Gateway 的 流量染色 和 灰度发布。
很多同学面试时被问用过 SpringCloud Gateway 吗?,只会说做限流、鉴权,但这些都是网关的基础操作。要想出去吹,得说用网关解决线上新版本平稳上线的问题。比如今天要分享的流量染色 + 灰度发布,就是我司每次上线必用的核心方案。
什么是流量染色?为什么需要它?
很多同学听流量染色觉得抽象,其实一句话就能说透:给请求打身份标签,让链路中所有服务都能认得出它。
比如我们做电商 APP 的新功能上线,想让 VIP 用户优先试用新版本,但普通用户继续用旧版本。怎么让订单、支付、库存这些下游服务知道当前请求是 VIP 用户的?
这时候就需要染色:请求进入网关时,判断用户身份是 VIP,就在请求头里加一个 X-Traffic-Tag: vip 的标识,这个过程就是流量染色。
后续的订单服务拿到请求,看到 X-Traffic-Tag: vip,就走新版本的订单逻辑;支付服务看到这个标签,就用新的支付接口;甚至日志系统看到这个标签,都会单独记录VIP 新版本的日志,单独处理这部分请求。
流量染色的核心价值在于,打破所有流量无差别处理的局限。有了染色标签,灰度发布、A/B 测试、环境隔离(比如测试流量不进生产库)才能落地。
什么是灰度发布?
搞懂了流量染色,灰度发布就好理解了,基于染色标签,让部分流量走新版本,逐步验证稳定性。
以前我们没做灰度时,上线都是一刀切:凌晨 2 点全量切换新版本,一旦出问题,所有用户都受影响,只能紧急回滚,既狼狈又容易丢数据。
现在用灰度发布,流程变成这样:
上线前:只让内部测试账号(染色标签 X-Traffic-Tag: test)走新版本,验证功能没问题;
上线初期:放 5% 的 VIP 用户(标签 vip)走新版本,观察日志和监控;
上线中期:没问题就扩大到 30%、50% 的 VIP 用户;
全量:确认稳定后,所有用户切换到新版本,灰度结束。
如果中间发现问题,比如 5% 的 VIP 用户反馈下单失败,直接把灰度规则关掉,所有流量切回旧版本,影响范围只有 5%,风险完全可控。
常见的灰度策略除了按用户标签,还有这些:
按比例:10% 流量走新版本(比如用用户 ID 取模,ID 尾号为 0 的用户);
按业务场景:只让 “新用户注册” 接口走新版本,老用户接口不变;
按设备:iOS 用户先切新版本,Android 用户后续再切(避免不同设备适配问题同时爆发)。
实现流量染色 + 灰度发布
接下来是重点:基于 SpringCloud Gateway,如何写代码实现这两个功能?整个流程分几步:请求染色→灰度路由→效果验证,所有代码都是生产环境可直接复用的。
项目依赖
首先确保引入 Gateway 核心依赖(Spring Boot 2.7.x + Spring Cloud Alibaba 2021.0.4.0 版本):
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 用于服务发现(如果灰度路由到注册中心的服务) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
第一步:实现流量染色
流量染色的核心是拦截所有请求,按规则打标签,用 Gateway 的 GlobalFilter 就能实现,所有请求都会经过这个过滤器,我们在这里判断用户身份,注入染色标签。
比如我们的规则是:
如果请求参数里有
userType=vip,就给请求头加X-Traffic-Tag: vip;如果请求参数里有
userType=test,就加X-Traffic-Tag: test;其他请求默认加
X-Traffic-Tag: normal。
代码实现:
@Configuration
public class TrafficDyeFilterConfig {
// 定义全局过滤器,Order设为-1(确保比其他过滤器先执行,早染色早用)
@Bean
@Order(-1)
public GlobalFilter trafficDyeFilter() {
return (exchange, chain) -> {
// 1. 获取请求中的用户标识(参数/Cookie)
String userType = getUserTypeFromRequest(exchange);
// 2. 根据用户类型设置染色标签
String trafficTag = getTrafficTagByUserType(userType);
// 3. 将染色标签注入请求头(传递给下游服务)
exchange.getRequest().mutate()
.header("X-Traffic-Tag", trafficTag)
.build();
// 4. 继续执行后续过滤器链
return chain.filter(exchange);
};
}
// 从请求参数或Cookie中获取用户类型
private String getUserTypeFromRequest(ServerWebExchange exchange) {
// 先查请求参数:比如 http://xxx?userType=vip
List<String> userTypeParams = exchange.getRequest().getQueryParams().get("userType");
if (userTypeParams != null && !userTypeParams.isEmpty()) {
return userTypeParams.get(0);
}
// 默认返回normal
return "normal";
}
// 根据用户类型映射染色标签
private String getTrafficTagByUserType(String userType) {
switch (userType) {
case "vip":
return "vip";
case "test":
return "test";
default:
return "normal";
}
}
}
关键说明:
Order(-1)很重要:确保染色过滤器比鉴权、限流过滤器先执行,避免后续逻辑拿不到染色标签;标签放在请求头
X-Traffic-Tag:下游服务(如订单服务)可以直接通过request.getHeader("X-Traffic-Tag")获取标签,做差异化处理;扩展性:如果需要更复杂的染色规则(比如按用户 ID 取模、按地区),直接在
getUserTypeFromRequest里加逻辑即可。
第二步:实现灰度路由
染色后,下一步就是让不同标签的流量走不同版本的服务,这需要自定义 RoutePredicateFactory(路由断言工厂),判断请求的染色标签,匹配对应的服务路由。
比如我们的灰度规则是:
染色标签为
vip或test的请求,路由到新版本服务(服务名order-service-v2);其他请求(标签
normal),路由到旧版本服务(服务名order-service-v1)。
自定义灰度断言工厂
// 自定义断言工厂,命名格式:XXXRoutePredicateFactory(固定后缀)
@Configuration
public class GrayRoutePredicateFactory extends AbstractRoutePredicateFactory<GrayRoutePredicateFactory.Config> {
// 染色标签的请求头名(和第一步的X-Traffic-Tag对应)
private static final String TRAFFIC_TAG_HEADER = "X-Traffic-Tag";
// 构造函数,指定配置类
public GrayRoutePredicateFactory() {
super(Config.class);
}
// 定义配置类:存储断言需要的参数(比如“需要匹配的染色标签”)
@Validated
public static class Config {
// 允许的染色标签(比如["vip", "test"])
@NotEmpty
private List<String> allowTags;
public List<String> getAllowTags() {
return allowTags;
}
public void setAllowTags(List<String> allowTags) {
this.allowTags = allowTags;
}
}
// 读取配置参数的顺序(和application.yml中配置的顺序对应)
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("allowTags");
}
// 核心逻辑:判断请求的染色标签是否在允许的列表中
@Override
public GatewayPredicate apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
// 1. 获取请求头中的染色标签
List<String> trafficTags = exchange.getRequest().getHeaders().get(TRAFFIC_TAG_HEADER);
if (trafficTags == null || trafficTags.isEmpty()) {
return false; // 没有标签,不匹配灰度路由
}
String trafficTag = trafficTags.get(0);
// 2. 判断标签是否在允许的列表中(比如["vip", "test"])
return config.getAllowTags().contains(trafficTag);
}
// 用于日志打印,方便调试
@Override
public String toString() {
return "GrayRoutePredicate{allowTags=" + config.getAllowTags() + "}";
}
};
}
}
配置网关路由
在配置文件 application.yml 中,用自定义的 GrayRoutePredicateFactory 配置路由规则,指定哪些标签的流量走哪个服务:
spring:
cloud:
gateway:
routes:
# 路由1:灰度流量(vip/test标签)→ 新版本服务(order-service-v2)
- id: gray_route_v2
uri: lb://order-service-v2 # 服务注册中心的新版本服务名
predicates:
# 自定义灰度断言:允许的标签是["vip", "test"]
- name: GrayRoute
args:
allowTags[0]: vip
allowTags[1]: test
# 匹配订单接口的路径(比如 /api/order/**)
- Path=/api/order/**
filters:
# 路径重写(可选,根据实际业务调整)
- RewritePath=/api/(?<segment>.*), /$\{segment}
# 路由2:普通流量(normal标签)→ 旧版本服务(order-service-v1)
- id: normal_route_v1
uri: lb://order-service-v1 # 旧版本服务名
predicates:
# 普通流量:不满足灰度断言,走这条路由
- Path=/api/order/**
filters:
- RewritePath=/api/(?<segment>.*), /$\{segment}
关键说明:
uri: lb://xxx:用lb协议表示从服务注册中心(如 Nacos)拉取服务实例,实现负载均衡;路由顺序:Gateway 按路由配置的顺序匹配,所以灰度路由(
gray_route_v2)要放在普通路由前面,确保灰度流量优先匹配;扩展性:如果需要按比例灰度(比如 10% 流量走 v2),可以在
GrayRoutePredicateFactory里加用户 ID 取模的逻辑,比如userID % 10 == 0才走 v2。
第三步:验证效果
代码和配置都做好后,验证是否生效,用 Postman 看是否路由到正确的服务:
请求地址:http://网关IP:网关端口/api/order/create?userType=vip,请求可以转发到 order-service-v2
线上环境要注意
刚才的代码是基础版,如果要在生产环境用还需要做 3 个优化,避免踩坑:
1. 染色标签的透传问题
如果下游服务还有多层调用(比如网关→订单服务→库存服务),要确保 X-Traffic-Tag 在整个调用链中传递,不能断。
如果你用 OpenFeign 做服务间调用,加一个 Feign 拦截器,自动把请求头中的 X-Traffic-Tag 传递下去:
@Component
public class FeignTrafficTagInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 从当前请求上下文获取染色标签(需要用ThreadLocal存储)
String trafficTag = TrafficTagContextHolder.get();
if (trafficTag != null) {
template.header("X-Traffic-Tag", trafficTag);
}
}
}
如果用 Dubbo,在 Dubbo 过滤器中做类似的头传递。
2. 灰度规则的动态调整
如果每次调整灰度比例(比如从 5% 到 30%)都要改代码、重启网关,效率太低。
把灰度规则(比如允许的标签、比例)存到 Nacos 配置中心;网关监听 Nacos 配置变更,动态更新灰度断言的规则,不用重启服务。
3. 灰度失败的快速回滚
如果新版本出问题,需要立刻把所有流量切回旧版本。
在 Nacos 中加一个灰度开关(比如 gray.switch=false);
自定义断言工厂时,先判断开关是否开启:如果开关关闭,直接不匹配灰度路由,所有流量走旧版本。
说在最后
网关不只是转发工具,更是流量控制中心。
很多同学把 SpringCloud Gateway 当成简单的转发工具,只用它做限流、鉴权,其实它的核心价值是控制流量的走向,通过流量染色给流量贴标签,通过灰度路由让流量走对路,这才是线上平稳上线的关键。
看到这说明你已经掌握了,所以下次面试再被问 Gateway,知道该怎么说了吧!
上线别再“一刀切”!Gateway 做流量染色 + 灰度发布,告别线上事故的更多相关文章
- 流量染色与gRPC服务托管 微服务协作开发、灰度发布之流量染色 灰度发布与流量染色
大规模微服务场景下灰度发布与流量染色实践 https://mp.weixin.qq.com/s/UBoRKt3l91ffPagtjExmYw [go-micro]微服务协作开发.灰度发布之流量染色 - ...
- Knativa 基于流量的灰度发布和自动弹性实践
作者 | 李鹏(元毅) 来源 | Serverless 公众号 一.Knative Knative 提供了基于流量的自动扩缩容能力,可以根据应用的请求量,在高峰时自动扩容实例数:当请求量减少以后,自动 ...
- 使用tcpcopy复制线上流量进行测试
使用tcpcopy复制线上流量进行测试 online server 线上服务所在机器 10.136.11.4 部署tcpcopy sudo /usr/local/tcpcopy/sbin/tcpcop ...
- gor实现线上HTTP流量复制压测引流
一.使用背景 gor 是一款go语言实现的简单的http流量复制工具,它的主要目的是使你的生产环境HTTP真实流量在测试环境和预发布环境重现.只需要在 代理例如nginx入口服务器上执行一个进程,就可 ...
- Redis线上环境做Keys匹配操作!你可以离职了!
转自:https://blog.csdn.net/bntx2jsqfehy7/article/details/84207884一.一个新闻 新闻内容如下:php工程师执行redis keys * 导致 ...
- 出现线上bug,测试人能做些什么?
测试奇谭,BUG不见. 大家好,我是谭叔. 一提到线上问题,很多测试小白要么"原则性"恐惧,要么憨憨如也,不知如何下手. 本篇文章,我再细化下这道常见的面试题,跟大家捋捋发生线上问 ...
- Spring Cloud GateWay基于nacos如何去做灰度发布
如果想直接查看修改部分请跳转 动手-点击跳转 本文基于 ReactiveLoadBalancerClientFilter使用RoundRobinLoadBalancer 灰度发布 灰度发布,又称为金丝 ...
- 转载:APP的上线和推广——线上推广渠道
本文版权归个人所有,如需转载请注明出处http://www.cnblogs.com/PengLee/p/4637080.html 目录 应用商店 互联网开放平台 软件下载中心 媒体社交平台 刷榜推广 ...
- 使用Fabric一键批量部署上线/线上环境监控
本文讲述如何使用fabric进行批量部署上线的功能 这个功能对于小应用,可以避免开发部署上线的平台,或者使用linux expect开发不优雅的代码. 前提条件: 1.运行fabric脚本的机器和其他 ...
- 使用tcpcopy导入线上流量进行功能和压力测试
- 假设我们要上线一个两年内不会宕机的先进架构.在上线前,免不了单元测试,功能测试,还有使用ab,webbench等等进行压力测试. 但这些步骤非生产环境下正式用户的行为.或许你会想到灰度上线,但毕竟 ...
随机推荐
- 独立开发问题记录-margin塌陷
一.概述 往事如风,一周就过去了. 上周在Figma里指点江山,这周在前端代码里卑微搬砖. 回想上周,在Figma中排列组合,并且精确到1像素.每设计出一个页面,成就感就蹭蹭往上涨. 没想到还没沾沾自 ...
- LoRa产品在智慧畜牧养殖场景的解决方案
案例背景: 在全球农业发展格局中,畜牧业是否成为农业支柱产业,是衡量一个国家农业现代化程度的关键标尺: 科学研究与生产实践表明,环境因素在家禽养殖中起着决定性作用.尤其是在封闭式畜禽舍环境下,光照条件 ...
- 前端开发系列076-JQuery篇之框架源码解读[插件]
这篇文章将主要介绍jQuery框架的插件机制,包括但不限于jQuery.extend和jQuery.fn.extend方法的设计和使用,JavaScript体系中的常用概念以及jQuery插件的使用等 ...
- Codeforces Round #697 (Div. 3) ABCDE 题解
久违的cf服务器爆炸场 A. Odd Divisor 思路:任何一个数都可以写成\(n = k2^m,其中k是一个奇数\),若k=1,那么n就一定是一个2的幂. view code #include& ...
- 使用ipad阅读代码
简介 使用ipad阅读代码 https://www.zhihu.com/collection/64510384 https://www.cnblogs.com/benjamin-t/p/3618787 ...
- iPaaS中API接口管理平台的作用
在iPaaS中,API接口管理平台具有关键作用,主要起到集成和连接不同系统之间的API,支持API文档和元数据管理,从代码注解扫描生成API.Swagger导入API.API自动识别.手工注册等多种方 ...
- ETLCloud中如何执行Java Bean脚本
ETLCloud中如何执行Java Bean脚本 在ETLCloud这一强大的数据集成和转换平台中,执行Java Bean脚本的能力为其增添了更多的灵活性和扩展性.Java Bean脚本不仅仅是一段简 ...
- MySQL 17 如何正确地显示随机消息?
假设有一个场景,一个英语学习APP首页有一个随机显示单词的功能,用户每次访问首页的时候,都会随机滚动显示三个单词. 已知表里有10000条记录,来看看随机选择3个单词有什么方法,又存在什么问题. 建表 ...
- FreeSwitch Hangup-Cause电话挂断原因速查
Freeswitch官网太慢了,经常还打不开,把电话挂断原因大全复制一份到这里,方便日常查看 ITU-T Q.850 Code SIP Equiv. Enumeration Cause Descrip ...
- OpenDeepWiki:AI驱动的代码知识库文档生成技术深度解析
项目地址 Git仓库: https://github.com/AIDotNet/OpenDeepWiki 在线体验: https://opendeepwiki.com 本文档基于: 当前本地仓库分析 ...