Spring Cloud Gateway整合Sentinel实现网关限流与动态规则实践
2026/9/7 1:16:24 网站建设 项目流程

简介:这是一份Spring Cloud Gateway整合Sentinel实现网关限流的PDF图文教程,内含1个PDF文件、共37KB,适合微服务架构开发者快速了解网关级流量控制。已有13505人学习或下载,关注度较高。内容以父工程为基础创建子工程,依次给出spring-cloud-starter-gateway、sentinel、sentinel-gateway扩展以及Nacos服务发现等依赖,并结合application.yml配置Nacos地址、Sentinel Dashboard传输端口、scg.fallback兜底响应及路由规则。同时介绍启动类与Sentinel控制台设置流控规则的方法,覆盖QPS、线程数、系统负载等条件。整体从依赖、配置到控制台操作形成闭环,便于读者直接对照落地,统一管理入口流量并保护后端微服务。 做微服务网关限流,我前前后后对比过好几种方案,最后在Spring Cloud Gateway里接入Sentinel才算把整条链路理顺。网关是所有外部流量的第一道闸门,限流放在这一层,既能保护后面的业务服务,又能让限流策略在一个地方统一管理,不用每个服务各写一套。这篇文章把我项目里Spring Cloud Gateway整合Sentinel实现网关限流的过程完整拆开讲:依赖怎么引、规则怎么配、怎么通过Nacos动态更新规则、限流后返回什么给前端,以及我踩过的几个坑,适合正在做微服务网关、想给系统加一层限流保护的朋友直接参考。

1. 网关限流的定位与方案选型

1.1 为什么限流要放在网关这一层

微服务架构下,流量入口从单体应用变成了API网关,这是一个非常适合做统一治理的位置。我坚持在网关层做限流,而不是在每个业务服务里各自加一个限流注解,原因有三点。

第一,入口统一,规则好管理。所有请求都会先经过网关,在这一个地方配置QPS阈值、并发线程数,比把规则分散在十几个服务里明显更容易维护。第二,保护范围更完整。很多限流事故的起因是调用链上游瞬时流量把下游打满,如果服务各自限流,很难形成全局视角,而网关限流面对的是真实流量总量,能更早拦截风险。第三,成本可控。在网关层做限流,业务服务不需要改代码、不需要引依赖,对团队协作的侵入性很小。

有一次线上做活动,某个订单接口的流量瞬间涨了快十倍,当时网关限流规则生效,直接把多余的请求挡住,返回了一个友好的提示,下游服务稳稳当当。如果这层保护放在每个服务里,可能等发现的时候,数据库连接池已经被打满了。

1.2 Sentinel与Gateway自带限流方案的对比

Spring Cloud Gateway自带了一个RequestRateLimiter过滤器,基于Redis和令牌桶算法实现限流。这套方案能跑,我也在早期项目里用过,但用久了会发现几个痛点:规则写死在配置里,调整阈值要改配置重新发布,虽然可以用配置中心动态刷新,但响应链路比较绕;只支持令牌桶这一种模型,粒度也比较粗,想区分不同API分组做精细控制不太顺手;缺少控制台和监控面板,限流效果只能靠日志和监控曲线观察。

后来换到Sentinel,原因很直接。Sentinel本身就是阿里开源的流量防卫组件,限流、熔断、系统保护都有,而且对Spring Cloud Gateway有官方适配模块。接入之后,限流规则可以通过控制台可视化配置和查看,规则变更也能通过Nacos等配置中心动态下发,网关进程不需要重启。此外,Sentinel支持QPS、线程数、关联限流、链路限流等多种规则,在网关场景下虽然不是全部都需要,但选择空间明显更大。

1.3 网关集群部署时对限流的影响

这里顺便回应一个经常被问的问题:Spring Cloud Gateway能做集群吗?答案是当然能,网关本来就是无状态服务,前面挂负载均衡器,后面多个Gateway实例水平扩展,这是非常标准的部署方式。但集群部署后,限流统计口径就发生了变化。

Sentinel默认的限流是单机维度,每个网关实例各自统计各自的QPS。如果一共3个实例,每个实例限流阈值是1000,整条链路实际放进去的流量可能接近3000。如果希望全局限流,就需要引入Sentinel集群限流能力,让各实例向Token Server申请令牌。我个人的建议是,在没有强一致的全局限流需求之前,优先用单机配额乘以实例数的办法,操作简单,效果也足够。集群限流的部署和运维成本都不低,不是所有团队都有必要上。

2. 基础整合:依赖、配置与链路验证

2.1 版本选型与依赖引入

版本选型是很多新手踩坑的第一个环节。Spring Cloud Alibaba、Spring Boot、Spring Cloud、Sentinel四者之间存在版本对应关系,直接引最新版经常会出现过滤器和规则不生效的诡异问题。我项目里用的这一组是比较稳定常见的:Spring Boot 2.6.13、Spring Cloud 2021.0.5、Spring Cloud Alibaba 2021.0.5.0,对应Sentinel 1.8.6。如果你的项目是Spring Boot 2.7.x,也可以考虑Spring Cloud Alibaba 2022.0.0.0,具体对齐方式以官方版本说明为准,正式开发前最好先用一个小demo跑一遍链路。

在maven中引入依赖,核心是两个:

<!-- Sentinel核心依赖,版本由Spring Cloud Alibaba统一管理 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!-- Gateway适配依赖,让Sentinel能识别网关路由资源 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency>

这里特别提醒:第二个gateway适配包很容易漏掉。只引入starter不会报错,但Sentinel无法感知网关路由,后续配置GatewayFlowRule根本不生效。我第一次接入时就踩过这个坑,只加了starter就跑去配规则,结果控制台看不到任何一个网关资源。

2.2 网关配置Sentinel控制台连接

在application.yml里补充Sentinel控制台地址。我本地调试时控制台是用docker compose启动的,容器暴露8858端口,客户端地址直接写宿主机IP和端口就行。

spring: cloud: sentinel: transport: dashboard: 127.0.0.1:8858 port: 8719 eager: true

transport.port是Sentinel客户端与控制台通信的本地端口,默认8719,如果被占用会自动扫描下一个可用端口。eager这个配置值得说一下,我习惯设成true,让应用启动时就主动连接控制台完成注册,否则要等第一次请求进来才会注册,排查问题时容易产生“怎么控制台看不到服务”的错觉。

2.3 最小可用链路验证

依赖和配置都做完之后,先不要急着写限流规则,先验证链路通不通。启动网关应用,打开Sentinel控制台,如果能看到对应的网关服务实例,说明客户端注册成功。这时到“网关监控”标签页,如果里面能列出网关路由ID,说明适配器也生效了。

我一般会在这个阶段先确认资源能正确展示,再继续写规则。控制台里看不到网关路由ID的原因,最可能的就是版本不匹配或者gateway适配包没引入,把这两个问题排除掉,链路基本就通了。

3. 路由级限流与自定义API分组

3.1 限流规则的资源模型

在网关场景里接入Sentinel,首先要理解资源模型。Sentinel把需要保护的东西抽象成resource,在普通微服务里,一个方法、一个接口路径都可以作为资源。在网关场景里,默认情况下,一个Spring Cloud Gateway的路由ID就是一个资源。也可以把一组路径聚合起来,定义成一个自定义API分组,再把分组当作资源来限流。

对应到规则类型上,网关限流使用的不是普通FlowRule,而是GatewayFlowRule。它和FlowRule的区别在于,除了基本的限流阈值、限流维度之外,还多了resourceMode字段,用来标识当前资源是路由ID还是自定义API分组,另外还支持URL参数维度、Header维度等更偏网关场景的配置。我第一次使用的时候一直用FlowRule去配网关资源,结果怎么都不生效,后来才意识到网关场景要走自己的规则体系。

3.2 注册GatewayFlowRule的完整代码

下面这个配置类是接入网关限流最核心的一段代码,我直接把常用模板贴出来。

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import javax.annotation.PostConstruct; import java.util.Arrays; import java.util.HashSet; import java.util.Set; @Configuration public class GatewaySentinelConfig { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { return new SentinelGatewayBlockExceptionHandler(); } @Bean @Order(-1) public SentinelGatewayFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } @PostConstruct public void doInit() { initGatewayRules(); initApiDefinitions(); } private void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("order_route") .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID) .setGrade(RuleConstant.FLOW_GRADE_QPS) .setCount(100) .setIntervalSec(1)); GatewayRuleManager.loadRules(rules); } private void initApiDefinitions() { Set<ApiDefinition> definitions = new HashSet<>(); ApiDefinition api = new ApiDefinition("order_api_group") .setPredicateItems(new HashSet<>(Arrays.asList( new ApiPathPredicateItem() .setPattern("/api/order/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX) ))); definitions.add(api); GatewayApiDefinitionManager.loadApiDefinitions(definitions); } }

这里有几个点需要解释清楚。filter的order我设置的是-1,原因是希望限流过滤器排在大多数内置过滤器之前,确保限流判断发生在路由转发之前。resourceMode为RESOURCE_MODE_ROUTE_ID时,资源名对应的是路由ID,适合精确控制某一条路由。如果多个路由都指向同一组业务接口,建议用自定义API分组做聚合限流,规则更简洁、更好理解。

3.3 自定义API分组的匹配策略

自定义API分组本质上是一个路径匹配规则的集合,它支持三种匹配策略:精确匹配、前缀匹配和正则匹配。实际项目中前缀匹配用得最多,对RESTful接口支持最好,像/api/order/、/api/user/这类的路径,一条前缀规则就能把子路径都覆盖住。

匹配策略对应三个常量:URL_MATCH_STRATEGY_EXACT、URL_MATCH_STRATEGY_PREFIX、URL_MATCH_STRATEGY_REGEX。同一个API分组下可以设置多个predicateItem,它们之间是或的关系,命中任意一个路径就算命中该分组,这给多模块网关聚合限流提供了很大弹性。

4. 规则动态化:接入Nacos持久化下发

4.1 为什么规则不能写死在代码里

写死在代码里的限流规则有一个严重问题:规则变更必须重新发布应用。一个限流阈值的调整往往是因为线上出现了流量波动,这时候最需要的恰恰是快速调整能力,而不是走一轮发布流程。我之前的做法是把规则写在配置类里,有一次大促前调整阈值,光审批加发布就折腾了半个多小时,非常被动。

代码写死还意味着不同环境的规则没法分开管理,测试环境压测通过的值和生产环境往往不一样。所以我会建议从零接入的项目直接按照规则动态化的方式来设计,省得后面再重构。

4.2 控制台配置与配置中心下发的取舍

目前Sentinel规则管理大体上有两种思路:一种是通过控制台手动配置,另一种是通过配置中心下发。控制台配置的优势是方便,图形界面点一点就生效,调试阶段非常好用。缺点也很明显,规则默认保存在内存里,服务重启后就丢了,多人同时操作时容易互相覆盖。

配置中心下发的方式则把规则当成配置来处理,Nacos或Apollo都可以,规则变更走配置审核流程,环境隔离也自然,服务实例从配置中心拉取规则,即使重启也不会丢失。我的建议是:开发调试阶段用控制台就够了,线上环境尽量走Nacos下发。

4.3 通过Nacos动态更新GatewayFlowRule

需要注意,Spring Cloud Alibaba的Sentinel数据源模块对普通FlowRule、DegradeRule支持比较完善,但GatewayFlowRule的自动同步需要自己处理,这一点官方适配并不像普通规则那样开箱即用。我采用的方式是让网关应用启动时连接Nacos,监听一个专门的网关限流规则配置,配置变化时把JSON解析成GatewayFlowRule集合,重新加载到GatewayRuleManager中。

监听配置的核心代码如下:

@Component public class GatewayRuleNacosListener { private static final String DATA_ID = "gateway-flow-rules"; private static final String GROUP = "SENTINEL_GROUP"; @PostConstruct public void init() throws NacosException { ConfigService configService = NacosFactory.createConfigService( new Properties() {{ put("serverAddr", "127.0.0.1:8848"); put("namespace", "public"); }}); String ruleJson = configService.getConfigAndSignListener( DATA_ID, GROUP, 5000, new Listener() { @Override public Executor getExecutor() { return null; } @Override public void receiveConfigInfo(String configInfo) { if (configInfo == null || configInfo.isEmpty()) { return; } List<GatewayFlowRule> flowRules = JSON.parseArray(configInfo, GatewayFlowRule.class); GatewayRuleManager.loadRules(new HashSet<>(flowRules)); } }); // 启动时立即加载一次规则,避免空窗期 if (ruleJson != null && !ruleJson.isEmpty()) { List<GatewayFlowRule> flowRules = JSON.parseArray(ruleJson, GatewayFlowRule.class); GatewayRuleManager.loadRules(new HashSet<>(flowRules)); } } }

Nacos中对应配置的JSON内容大概长这样:

[ { "resource": "order_api_group", "resourceMode": 1, "grade": 1, "count": 100, "intervalSec": 1 }, { "resource": "user_route", "resourceMode": 0, "grade": 0, "count": 50, "intervalSec": 1 } ]

字段含义我用表格整理一下:

字段含义取值说明
resource限流资源名路由ID或自定义API分组名称
resourceMode资源模式0表示路由ID,1表示自定义API分组
grade限流维度0表示并发线程数,1表示QPS
count阈值大于0的整数
intervalSec统计时间窗口单位秒,配合grade使用

这段代码本身不复杂,但第一次运行时很容易忽略启动时立即加载这一步。如果只监听不加载,应用刚启动时规则是空的,要等下一次配置变更才能生效,这会留下一个空窗期,我在这里吃过亏,特意标注出来。

5. 限流命中后的处理与自定义响应

5.1 默认限流响应的现状

当网关触发Sentinel限流时,默认返回的是纯文本“Blocked by Sentinel: FlowException”,状态码是429。对于内部调试来说,这个提示足够直观,但如果面向真实用户,前端页面或移动端App看到这么直白的提示显然不合适。用户应该看到的是一个统一格式的JSON,比如code为429、msg为“系统繁忙,请稍后重试”。

5.2 自定义BlockRequestHandler

Sentinel在网关场景中提供了SentinelGatewayBlockExceptionHandler,我们要做的是替换它内部的BlockRequestHandler实现。下面是我项目里的写法:

@Bean public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { BlockRequestHandler blockRequestHandler = (exchange, throwable) -> { log.warn("[gateway-limited] uri={}, exception={}", exchange.getRequest().getURI(), throwable.getMessage()); Map<String, Object> result = new HashMap<>(); result.put("code", 429); result.put("msg", "请求过于频繁,请稍后再试"); result.put("success", false); return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(result)); }; return new SentinelGatewayBlockExceptionHandler(blockRequestHandler); }

配置完这个Bean之后,所有被限流拦截的请求都会返回统一的JSON结构。我给前端约定的格式是code、msg、success三个字段,网关返回429的同时,JSON里也能解析出业务层需要的错误信息。这种做法比让前端根据HTTP状态码自己猜原因可靠得多。

5.3 限流日志与链路观测

限流日志主要看两个地方。第一个是应用日志,被限流时会抛出BlockException,在自定义BlockRequestHandler里主动打一条warn级别的日志会比较有用,后面排查问题时会省很多力气。第二个是Sentinel控制台的“实时监控”与“网关监控”,可以直观看到每个资源的通过QPS和拒绝QPS。

我在定位限流阈值是否合理时,通常先看控制台曲线,再结合应用日志确认个别请求被拦截的具体时间,结合起来判断是阈值偏低还是流量确实突增。如果日志里大量出现同一个资源的BlockException,但控制台拒绝QPS并不高,那大概率是规则资源名与实际资源对不上,这个信息可以帮助快速锁定问题。

6. 常见问题排查与踩坑实录

6.1 限流规则配置了但不生效

遇到限流不生效,我一般按下面这个顺序排查。

第一步看依赖,确认spring-cloud-starter-alibaba-sentinel和spring-cloud-alibaba-sentinel-gateway两个依赖都在,特别是第二个,少了它Sentinel根本感知不到网关。第二步看控制台,服务是否注册,网关路由资源是否出现在“网关监控”列表里,如果资源列表是空的,规则自然找不到目标。

第三步看资源模式,规则里的resource名和资源实际名称是否一致。路由ID限流模式下资源必须是Spring Cloud Gateway配置的路由ID,自定义API分组模式下资源必须是ApiDefinition的名称,两边对不上规则不会触发。第四步看过滤器的order,如果网关里有其他过滤器优先级更高并且提前做了转发,Sentinel过滤器可能根本没机会执行限流逻辑。

6.2 控制台看不到网关资源

控制台里看不到网关资源,最常见的三个原因:版本不匹配、适配包缺失、客户端没有注册成功。

版本问题最隐蔽,Spring Cloud Alibaba版本和Sentinel控制台版本如果相差太大,数据传输协议可能不兼容,客户端日志会报连接失败。我的建议是,控制台版本尽量和依赖中Sentinel的核心版本保持一致,不要混用。另外,eager这个配置建议打开,否则没有请求进来之前控制台看不到这个服务,容易被误判为接入失败。

还有一个细节是端口。客户端向控制台注册时会占用一个本地端口,如果服务器上已有其他进程占用了8719,Sentinel会自动切换端口,但控制台展示的注册信息里端口如果没对上,也会出现资源列表为空的情况。遇到这种问题,去客户端日志里搜索“Sentinel token server”相关的启动信息,能看到实际绑定端口。

6.3 集群部署时的几个注意点

最后聊一下集群部署。如果网关有多个实例,每个实例都需要连接同一个Nacos和同一个Sentinel控制台,配置中心下发规则时所有实例都会收到变更。默认的单机限流模式下,每个实例的阈值是相同的,实例数3、每个实例QPS阈值1000,整体放行量就接近3000。

如果业务上对总量有严格要求,比如只能承受5000 QPS,就需要把每个实例阈值除以实例数,或者引入Sentinel集群限流。集群限流的方案需要额外部署Token Server,应用接入也会复杂一些。根据我目前的项目经验,绝大多数场景单机限流乘以实例数就够用了,不要一上来就上集群限流,避免过度设计。

就我自己的实践来看,网关层限流先跑通单机方案,把规则动态下发和监控做完善,再等确实有总量控制需求时去碰集群限流,风险会小很多。项目里最先受益的其实是下游服务和运维同学,一个稳定的网关限流层,能把很多潜在的稳定性问题在入口处就化解掉,这比后续写多少兜底逻辑都有效。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询