十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Cloud Gateway整合Sentinel实现网关限流与动态规则实践

Spring Cloud Gateway整合Sentinel实现网关限流与动态规则实践 简介这是一份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 groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-sentinel/artifactId /dependency !-- Gateway适配依赖让Sentinel能识别网关路由资源 -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-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: truetransport.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() { SetGatewayFlowRule 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() { SetApiDefinition 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; } ListGatewayFlowRule flowRules JSON.parseArray(configInfo, GatewayFlowRule.class); GatewayRuleManager.loadRules(new HashSet(flowRules)); } }); // 启动时立即加载一次规则避免空窗期 if (ruleJson ! null !ruleJson.isEmpty()) { ListGatewayFlowRule 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表示路由ID1表示自定义API分组grade限流维度0表示并发线程数1表示QPScount阈值大于0的整数intervalSec统计时间窗口单位秒配合grade使用这段代码本身不复杂但第一次运行时很容易忽略启动时立即加载这一步。如果只监听不加载应用刚启动时规则是空的要等下一次配置变更才能生效这会留下一个空窗期我在这里吃过亏特意标注出来。5. 限流命中后的处理与自定义响应5.1 默认限流响应的现状当网关触发Sentinel限流时默认返回的是纯文本“Blocked by Sentinel: FlowException”状态码是429。对于内部调试来说这个提示足够直观但如果面向真实用户前端页面或移动端App看到这么直白的提示显然不合适。用户应该看到的是一个统一格式的JSON比如code为429、msg为“系统繁忙请稍后重试”。5.2 自定义BlockRequestHandlerSentinel在网关场景中提供了SentinelGatewayBlockExceptionHandler我们要做的是替换它内部的BlockRequestHandler实现。下面是我项目里的写法Bean public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { BlockRequestHandler blockRequestHandler (exchange, throwable) - { log.warn([gateway-limited] uri{}, exception{}, exchange.getRequest().getURI(), throwable.getMessage()); MapString, 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这个配置建议打开否则没有请求进来之前控制台看不到这个服务容易被误判为接入失败。还有一个细节是端口。客户端向控制台注册时会占用一个本地端口如果服务器上已有其他进程占用了8719Sentinel会自动切换端口但控制台展示的注册信息里端口如果没对上也会出现资源列表为空的情况。遇到这种问题去客户端日志里搜索“Sentinel token server”相关的启动信息能看到实际绑定端口。6.3 集群部署时的几个注意点最后聊一下集群部署。如果网关有多个实例每个实例都需要连接同一个Nacos和同一个Sentinel控制台配置中心下发规则时所有实例都会收到变更。默认的单机限流模式下每个实例的阈值是相同的实例数3、每个实例QPS阈值1000整体放行量就接近3000。如果业务上对总量有严格要求比如只能承受5000 QPS就需要把每个实例阈值除以实例数或者引入Sentinel集群限流。集群限流的方案需要额外部署Token Server应用接入也会复杂一些。根据我目前的项目经验绝大多数场景单机限流乘以实例数就够用了不要一上来就上集群限流避免过度设计。就我自己的实践来看网关层限流先跑通单机方案把规则动态下发和监控做完善再等确实有总量控制需求时去碰集群限流风险会小很多。项目里最先受益的其实是下游服务和运维同学一个稳定的网关限流层能把很多潜在的稳定性问题在入口处就化解掉这比后续写多少兜底逻辑都有效。本文还有配套的精品资源点击获取
返回列表