Spring Cloud Gateway集群限流实战:Sentinel统一管控方案
简介:Spring Cloud Gateway 与 Sentinel 整合的网关限流实战指南,面向已掌握 Spring Boot/Cloud 基础、希望为微服务入口增加高可用防护的 Java 开发者。内容以 PDF 形式呈现,压缩包内共 1 份文档,大小仅 37KB,轻量易读。已有 13505 人学习下载,是社区中较受关注的限流方案资料。文档从创建子工程说起,完整梳理 pom.xml 中 gateway、sentinel、nacos 等关键依赖的引入方式,并给出 application.yml 中 Nacos 地址、Sentinel Dashboard、scg.fallback 响应配置等核心参数示例,同时说明启动类注解和 @EnableDiscoveryClient 的使用要点。借助 Sentinel 控制台,开发者可为不同路由配置基于 QPS、线程数、系统负载的流控规则,并利用降级、熔断与黑白名单机制统一守护后端服务。整体内容从工程搭建到规则配置均有示例,适合作为网关层接入 Sentinel 的快速参考,能帮助读者跳过常见配置坑,直接完成基础限流环境搭建并理解动态规则调整思路。 spring cloud gateway能做集群吗?能做,而且多数生产环境早就是多实例部署了。可集群起来之后,限流这笔账怎么算,才是真正让人头疼的地方。我上个月接手的网关项目就是这样:Nginx在前面做负载均衡,后面挂了四个Spring Cloud Gateway实例,平时看着挺稳,结果大促流量一冲,订单服务先崩了。当时用的还是Gateway自带的RequestRateLimiter加Redis令牌桶,单实例限500 QPS。查日志才发现,四个实例各限各的,实际放过去的流量接近2000 QPS,下游不被打爆才怪。这个事让我下决心把Sentinel引入网关层做统一限流,前前后后折腾了一周多,把路由级限流、API分组限流、集群流控都跑通了。这篇文章把选型过程、落地步骤和踩过的坑完整记录下来,给正在网关层做限流的同学一个参考。
1. 网关限流选型:为什么最后定了Sentinel
1.1 网关层限流到底解决什么问题
微服务架构里,网关是所有流量的总入口,也是防御的第一道关卡。下游服务当然可以各自做限流,但等到请求穿透到业务服务再被拦住,数据库连接、线程池、内存这些资源已经被消耗了一轮。更麻烦的是,如果网关层不设防,突发流量会在很短时间内把多个服务一起打垮,雪崩起来是链式的。网关限流的作用就是用最小的成本把无效流量挡在门外,保护下游。
很多团队一开始会用Gateway自带的RequestRateLimiter,它本身没有大问题,但有几个现实痛点:KeyResolver要自己写,规则都在代码里,调阈值要改代码重新发版;用的Redis令牌桶在流量特别大的时候会有一定的误差;最关键的还是没有可视化看板,限流效果好坏全凭日志去猜。这些痛点在我接手项目时正好全部碰上,所以这次选型我就盯着一件事:要能可视化、要能动态调规则、要能在集群维度统一计量。
1.2 几种主流方案的取舍
我整理了一张对比表,是我当时的选型依据:
| 方案 | 实现成本 | 可视化 | 集群流控 | 规则动态下发 | 适用场景 |
|---|---|---|---|---|---|
| Gateway RequestRateLimiter | 低 | 无 | 近似(依赖中间件) | 不灵活 | 简单固定阈值 |
| Hystrix / Resilience4j | 中 | 弱 | 不支持 | 需自研 | 服务容错为主 |
| Sentinel 网关适配器 | 中 | 完善 | 支持 | 支持 | 网关+业务统一管控 |
Hystrix已经进入维护状态,Resilience4j在服务容错上更合适,但限流粒度、API分组能力在网关场景下都比较弱。Sentinel有独立控制台,能在页面上看到每个路由的实时流量,能在不改代码的情况下调整阈值,还有专门的网关适配器,所以最终选了它。
这里提个醒:网上搜“Sentinel”的时候,会混进来一个叫Hard Disk Sentinel的硬盘检测工具,那东西跟微服务限流没有半点关系,找文档时注意分辨,别浪费时间。
2. 整合前必须搞懂:Sentinel在网关里是怎么工作的
2.1 Gateway是WebFlux环境,别拿WebMVC的思路套
这是很多人第一次接Sentinel就翻车的地方。Spring Cloud Gateway底层是Spring WebFlux,不是传统的Servlet容器。Sentinel官方主推的方式是在Web应用里添加拦截器或者Filter,靠ThreadLocal做链路传递,这在Servlet环境下没有问题。但Gateway的请求处理模型是Reactor的,整个处理链路是异步的,普通那套Servlet过滤器并不完全适用。
官方专门为Gateway做了一套适配器,也就是 sentinel-spring-cloud-gateway-adapter 。它实现的不是Servlet Filter,而是一个GlobalFilter,作用在这个异步链路里,通过Reactor上下文完成流量计量和控制。所以整合前先记住一个结论:Spring Cloud Alibaba早期整合Gateway的方式已经逐步演进,现在更推荐直接使用官方网关适配器。
2.2 一次请求经过Sentinel的完整过程
把整个机制拆开看,涉及四个核心组件:
-
SentinelGatewayFilter:全局过滤器,在Gateway的过滤器链中执行,用来把每个进入的请求映射成Sentinel资源,并触发流量统计和规则校验。 -
GatewayFlowRule:网关限流规则,描述针对哪个资源限、限多少、什么统计周期、触发的控制行为。 -
ApiDefinition:API分组定义。路由级规则用RouteId做资源,API分组可以跨路由按URL模式匹配做资源聚合。 -
SentinelGatewayBlockExceptionHandler:请求被拦截后统一返回Block响应,官方也有留口子让你自定义返回内容。
一次完整链路是这样的:请求进入Gateway的WebHandler处理链后,SentinelGatewayFilter先拿到请求,根据当前匹配的路由ID解析出要校验的“资源”。如果配置了API分组,它还会按路径匹配判断请求属于哪个API分组。接着Sentinel基于滑动窗口统计这个资源的实时流量,如果窗口内QPS超过规则阈值,就把请求交给BlockExceptionHandler处理,直接返回限流响应,不再转发到下游;没超过就正常放行。
2.3 为什么不要直接往Gateway里塞普通Sentinel starter
真实情况中,很多同学图省事,直接把 spring-cloud-starter-alibaba-sentinel 加进Gateway项目,然后就遇到各种奇怪现象:要么启动时注册了重复的过滤器,要么监控页面里看不到预期的路由级资源,要么在WebFlux环境下链路异常。
原因是这个starter主要面向普通Web应用,在WebFlux环境下它也会尝试注册自己的拦截逻辑,再和网关适配器叠加,就相当于请求被计量了两次,规则和实际效果完全对不上。正确做法是:不用普通starter做网关适配,只保留官方 sentinel-spring-cloud-gateway-adapter ,传输和配置模块按需添加。
3. 从零落地:依赖、Dashboard与最小演示
3.1 Maven依赖怎么加
我用的版本是1.8.7,这也是目前生产环境比较稳的版本。核心依赖就两个:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.7</version>
</dependency>
第一个是网关适配器,第二个负责把当前服务心跳和监控数据上报给Dashboard,没有它Dashboard就看不到服务。这里要注意版本必须一致,不然可能出现 sentinel-core 被依赖树解析出多个版本,规则推送异常等莫名其妙的问题。
3.2 Dashboard启动:Docker Compose跑起来
Dashboard是规则管理和监控的可视化界面,本地调试可以先直接用Docker Compose起一个:
version: '3.8'
services:
sentinel-dashboard:
image: bladex/sentinel-dashboard:1.8.7
container_name: sentinel-dashboard
ports:
- "8858:8858"
environment:
- AUTH_USERNAME=sentinel
- AUTH_PASSWORD=sentinel
volumes:
- ./logs:/root/logs
然后执行 docker compose up -d ,打开 http://localhost:8858 ,账号密码就是 sentinel/sentinel 。这个镜像默认端口是8858,如果你是下载官方jar包运行,默认端口是8080,参数不一样,别照抄。
3.3 核心配置类与规则加载
我在项目里建了一个配置类,用来注册过滤器和定义初始规则:
@Configuration
public class SentinelGatewayConfig {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public SentinelGatewayConfig(
ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@PostConstruct
private void doInit() {
initApiDefinitions();
initGatewayRules();
}
private void initApiDefinitions() {
Set<ApiDefinition> definitions = new HashSet<>();
// 把 /order/** 这个URL前缀归为一个API分组,命名为 order_api
Set<ApiPredicateItem> items = Collections.singleton(
new ApiPathPredicateItem()
.setPattern("/order/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)
);
definitions.add(new ApiDefinition("order_api").setPredicateItems(items));
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
// 对 order_api 分组限流:每秒最多50个请求,超出后进入排队等待,最多等500ms
GatewayFlowRule rule = new GatewayFlowRule("order_api")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_API)
.setCount(50)
.setIntervalSec(1)
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
.setMaxQueueingTimeoutMs(500);
rules.add(rule);
GatewayRuleManager.loadRules(rules);
}
}
几个参数需要理解一下: count 是窗口内允许通过的请求数, intervalSec 是统计窗口秒数,两者配合就是QPS阈值; controlBehavior 两种最常用,一种是快速失败,一种是匀速排队,网关场景我建议用匀速排队,能平滑突发流量; maxQueueingTimeoutMs 是排队的最大耐心值,超过就拒绝。
启动时还要在VM参数里指定Dashboard地址和本机通信端口:
-Dcsp.sentinel.dashboard.server=localhost:8858
-Dcsp.sentinel.api.port=8719
8719是这个服务实例向Dashboard上报心跳、接收规则推送的端口,不要跟业务端口混淆。
3.4 怎么验证规则真的生效了
配置完成后,把Gateway服务启动,先到Dashboard确认“机器列表”里能看到这个网关实例。然后在测试环境对 /order/** 的接口做并发请求,比如用压测工具或直接写个循环脚本,以每秒100次的速率跑一小段时间。观察两端:一是请求返回体里出现 Blocked by Sentinel 类似的提示,说明被拦截了;二是Dashboard上order_api这个资源的实时QPS曲线被压到50的位置,不超过阈值线。如果这两点都符合,规则就生效了。
4. 限流粒度怎么定:路由级、API分组级、参数级
4.1 路由级限流:简单直接的总闸
路由级限流只针对某个RouteId,资源模式用 RESOURCE_MODE_ROUTE_ID 。它适合“整条业务线一个总量闸门”的场景。比如你的订单服务对应一个叫 order-service-route 的路由,那么这个路由下所有请求共享同一个QPS阈值,无论下面细分多少接口。
但它的缺点是粒度粗:如果同一个路由下既有高频查询接口又有低频写接口,用同一个阈值很容易误伤写操作。
GatewayFlowRule rule = new GatewayFlowRule("order-service-route")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID)
.setCount(200)
.setIntervalSec(1);
4.2 API分组级限流:按URL模式归类
API分组解决了“跨路由、按URL模式统一管控”的问题。比如你希望 /order/** 、 /payment/** 都归到 order_api 分组,统一限流500 QPS,那么用 ApiDefinition 定义分组,规则里资源模式设为 RESOURCE_MODE_API ,资源名写分组名即可。
匹配策略支持精确匹配和前缀匹配,我用最多的是前缀匹配。同一个API分组下可以挂多个匹配条件,彼此之间是或的关系,请求命中任何一个条件就算进了这个分组。
4.3 参数级限流:按调用方区分
还有一类场景需要按调用方做差异化限流。比如同一个接口,普通用户和合作方渠道的调用量完全不是一个量级。这时可以拿请求参数作为维度,常见的就是客户端IP、Host、或者某个Header。
GatewayFlowRule rule = new GatewayFlowRule("order_api")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_API)
.setCount(10)
.setIntervalSec(1)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP));
这段规则表示:order_api分组内,按客户端IP单独计数,每个IP每秒最多10个请求。参数级限流最有效也最容易被滥用,因为有些请求头是伪造的,要结合自身场景决定使用哪种解析策略。
4.4 三种粒度的组合思路
我实际项目里的做法是三层组合:路由级做总闸,API分组做业务线细分,参数级做重点来源方限制。表格整理如下:
| 粒度 | 适用场景 | 资源定义方式 | 典型阈值 |
|---|---|---|---|
| 路由级 | 整条链路总保护 | RouteId | 峰值QPS的1.5倍 |
| API分组 | 同类接口聚合管控 | ApiDefinition | 单接口峰值的2到3倍 |
| 参数级 | 按IP/渠道隔离 | GatewayParamFlowItem | 单来源方业务预算 |
顺序要清楚:先按路由匹配,再按API分组匹配,最后按参数维度拆分,Sentinel在内部就是按这条链分析资源的。
5. 集群场景下的限流:单机计量为什么不准,集群流控怎么做
5.1 网关集群是常态,别自己吓自己
针对“Spring Cloud Gateway能做集群吗”这个问题,答案是肯定的。Gateway本身是无状态应用,前面挂负载均衡器,后端挂多实例,这是标准部署方式。集群本身不是问题,问题在集群之后限流策略该怎么设计。
5.2 单机限流在集群下的致命误差
回到开头那个事故:每个网关实例都配了相同的规则,但Sentinel的计数动作默认发生在单台实例本地。四个实例每个限500 QPS,全局实际放行量可以到2000 QPS。这还不算每台实例之间流量分配不均的情况,有的实例跑了800,有的只有200,单机阈值看着设置了,整体防线形同虚设。
这种场景下,需要的是“集群流控”:让所有网关实例共享一个全局计数器,不管请求从哪个实例进来,都被同一个阈值约束。
5.3 集群流控的两种模式
Sentinel集群流控分两种模式:
- 嵌入模式:Token Server逻辑跑在某个业务节点内部。部署省事,但那个节点挂了会影响整个集群的限流判断,适合测试环境。
- 独立模式:单独的Token Server服务,所有网关节点作为Token Client连接它,每次请求都要向Server申请配额。多一次网络调用,但服务之间职责分离,适合生产环境。
生产上我推荐独立模式。引入 sentinel-cluster-server-default 和 sentinel-cluster-client-default ,在管理端配置集群规则并指定Token Server地址即可。真正落地时还要关注Token Server的高可用,可以在配置层面给它加个负载均衡,避免单点。
5.4 什么时候值得上集群流控
我的建议是:不要因为网上都说“集群限流好”就急着上。如果业务峰值就是几千QPS,单机限流的误差完全可以接受,多花精力在规则阈值上更实际。只有当单实例限流导致全局放行量明显超出下游承受能力,或者流量在实例之间严重不均时,集群流控才有必要。另外,集群流控对规则推送链路的稳定性要求更高,最好配合Nacos做规则持久化,否则Token Server一重启,全局计数器清零,后半晌流量直接穿透。
6. 落地过程中的几个大坑:完整排查链路
6.1 Dashboard看不到网关服务
这个问题的排查链路是固定的。先看8719端口有没有被监听: lsof -i:8719 ,没有输出说明transport模块没生效。再看启动日志里有没有Sentinel的心跳日志,或者直接把 -Dcsp.sentinel.dashboard.server 这个参数改成 -Dcsp.sentinel.heartbeat.interval.ms=1000 看看心跳间隔是否异常。最后确认网关项目里到底有没有引入 sentinel-transport-simple-http ,只引入适配器而没有transport,机器永远不会出现在Dashboard上。
6.2 规则重启就丢
内存规则天生不持久化。只要在代码里用 loadRules 加载,应用一重启就没了。生产环境的标准做法是把规则放到Nacos,用监听器实时同步到Gateway内存。启动时加载一次Nacos配置,后续通过Nacos发布配置,监听器自动更新 GatewayRuleManager 里的规则,不用改代码、不用重启,规则随时可控。
6.3 被限流之后返回的响应格式不统一
Gateway适配器的默认Block响应格式比较单一,前端拿到之后没法统一处理。我是通过 GatewayCallbackManager.setBlockHandler 定制的,直接在回调里返回标准JSON结构:
GatewayCallbackManager.setBlockHandler((exchange, t) -> {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return response.writeWith(Mono.just(response.bufferFactory()
.wrap("{\"code\":429,\"msg\":\"too many requests\"}"
.getBytes(StandardCharsets.UTF_8))));
});
注意这里一定要用WebFlux的异步响应方式 writeWith ,不要在里面做任何同步阻塞操作,否则Reactor线程模型会直接给你颜色看。
6.4 依赖冲突与核心版本被覆盖
排查这类问题最直接的方式是看依赖树: mvn dependency:tree | grep sentinel 。如果出现多个不同版本,就在父POM的 dependencyManagement 里统一指定Sentinel版本。尤其要注意业务模块里是否引入了其他组件间接依赖了旧版 sentinel-core ,这基本是“规则推送不生效”的常见元凶。
最后再分享一个实际体会:限流规则本质上是一份业务参数,不是代码,不要把规则配置写死在代码里。我这次做完后最大的收益不是网关挡住多少流量,而是规则全部迁移到了Nacos,每次大促前调阈值,只要改配置、发通知,不用再排队等发版。如果你正准备引入Spring Cloud Gateway和Sentinel这套组合,建议从第一版就把路由和API分组的命名规范定好,后面维护规则能少走很多弯路。
更多推荐



所有评论(0)