ARTICLE DETAIL

资讯详情

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

Sentinel 与 Spring Cloud Gateway 联动限流:构建微服务分层流量治理

Sentinel 与 Spring Cloud Gateway 联动限流:构建微服务分层流量治理 下午三点刚过监控大屏上那个代表核心接口流量的数字蹭一下就冲了上去。线上一个下单接口的QPS从平时的两千多几分钟内飙到接近两万网关层当时没有做任何保护所有请求都穿透到了下游服务数据库连接池瞬间被打穿告警群直接炸锅。虽然最后通过临时扩容顶住了但事后复盘就一句话限流这层必须前置到网关而且不能只做一层。那次事故之后我把网关和服务两个层面的流量治理重新做了一遍最终落地的就是今天要聊的这套 Sentinel Spring Cloud Gateway 联动限流方案。这套方案解决的核心问题是流量入口没有统一管控、单机限流误伤大流量节点、规则修改要靠重启生效。适合正在做微服务架构改造、被接口突发流量折磨过的开发同学和架构师参考。用过之后你会有一个明显的感受限流不是简单加个计数器而是要对入口流量、服务调用、异常降级做分层治理。1. 方案选型——为什么是 Sentinel 加上网关联动1.1 网关层限流和服务层限流根本不是一回事很多人一上来就纠结一个问题既然网关能限流为什么服务内部还要再来一层这个思路有问题。网关层限流和服务层限流保护的对象不一样缺一不可。网关层限流保护的是整个系统的入口管的是你来了多少我放进去多少。这一层解决了大流量直接冲垮下游的问题相当于小区门口的保安控制外来车辆进小区。服务层限流保护的是单个服务的核心资源管的是放进来之后每个接口能不能扛得住。这一层解决的是突发流量穿透网关后某个接口或某条数据库连接池被瞬间打满的问题。还有一个很多人忽略的点服务之间的内部调用是不经过网关的。比如订单服务调用库存服务这条链路走的是服务发现和负载均衡流量根本不经过网关。如果你的限流只做在网关层那服务间调用导致的雪崩你是完全看不见的。这也是为什么联动这个词很重要——网关层挡住入口流量服务层拦住内部调用和下游热点两层配合才能把整个链路保护好。1.2 常见限流方案横向对比在定方案之前我先把自己做过的几种限流方式摆在一起对比了一遍各有各的适用场景不能一棒子打死。方案限流粒度动态规则控制台熔断降级适合场景Redis Lua 令牌桶接口/Key需要自己写管理接口无无单机或小集群不想引入额外框架AOP 注解限流方法级别改代码重新发布无无简单业务规则很少变化Hystrix线程池/信号量改配置重启有简陋有老项目还在用但维护成本高Sentinel路由/接口/方法/热点控制台/配置中心热更新功能完善有中大型微服务需要动态治理从表格可以看出来Redis 令牌桶方案更适合做单机或轻量级限流AOP 注解适合规则几乎不动的小项目Hystrix 已经处在维护期。我需要的是既能动态调整规则、又能在网关层和服务层同时使用的方案Sentinel 在这里的优势非常明显。1.3 联动方案的整体思路我最终设计的联动结构是这样的网关层按路由维度设置一个总的入口阈值比如每个路由允许 5000 QPS进入服务层之后核心接口再设置自己的阈值比如下单接口允许 3000 QPS、库存查询接口允许 1500 QPS。这样流量从入口到内部阈值逐层收紧形成漏斗状的保护结构。规则全部放到 Nacos 配置中心统一管理Sentinel Dashboard 负责监控和规则下发。某个接口需要临时提高限额直接在控制台改一下推下去不用发版重启。这套结构下来网关层负责兜底服务层负责精细化控制台负责可视化配置中心负责持久化——每一层职责单一联动的价值就是在这个结构里体现的。2. 环境准备与基础集成2.1 版本组合怎么选Sentinel 和 Spring Cloud Gateway 集成最怕的就是版本不对导致的适配器加载失败。我先给出我实际验证过的组合组件版本Spring Boot2.6.13Spring Cloud2021.0.5Spring Cloud Alibaba2021.0.5.0Sentinel1.8.6Nacos2.2.3这里有个细节要注意Spring Cloud Alibaba 2021.0.x 对应的 Sentinel 适配器版本是 1.8.x不要直接拿 Sentinel 2.x 的最新版往上怼容易出现ClassNotFoundException或者NoSuchMethodError。如果你用的是 Spring Cloud 2022.x 以上的版本请同步确认 Spring Cloud Alibaba 对应版本不同大版本之间的 API 差异很大。2.2 引入依赖项目用的是 Maven核心依赖就几个。首先是 Spring Cloud Gateway 的基础依赖dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-gateway/artifactId /dependency然后是 Sentinel 的 Spring Cloud Alibaba 集成dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-sentinel/artifactId /dependency再就是网关适配器。这个是关键依赖没有它 Sentinel 不会感知 Gateway 的路由资源dependency groupIdcom.alibaba.csp/groupId artifactIdsentinel-spring-cloud-gateway-adapter/artifactId version1.8.6/version /dependency最后是 Nacos 数据源用于规则的动态拉取dependency groupIdcom.alibaba.csp/groupId artifactIdsentinel-datasource-nacos/artifactId version1.8.6/version /dependency引入sentinel-spring-cloud-gateway-adapter之后Spring Cloud Alibaba 的自动装配会自动检测到 Gateway 环境注册对应的 Filter。这一步不需要额外写代码但前提是适配器版本必须和 Sentinel 核心版本一致不然会出现 Filter 注册成功但规则不生效的诡异问题。2.3 基础配置依赖引入完之后在application.yml里做基础配置。这里有一个新手容易踩的坑Sentinel 客户端默认是懒加载的也就是说你什么都不配启动时不主动连控制台直到第一次访问链路才会初始化。我建议把eager打开方便在控制台第一时间看到服务spring: application: name: gateway-service cloud: nacos: discovery: server-addr: 192.168.1.100:8848 config: server-addr: 192.168.1.100:8848 file-extension: yaml sentinel: transport: dashboard: 192.168.1.100:8858 port: 8719 eager: true datasource: gateway-flow: nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} dataId: ${spring.application.name}-sentinel-flow groupId: SENTINEL_GROUP >spring: cloud: gateway: routes: - id: order-route uri: lb://order-service predicates: - Path/api/order/** filters: - StripPrefix1这个路由的 ID 是order-route所有以/api/order/开头的请求都会经过它转发到订单服务。Sentinel 进行网关限流时可以直接针对order-route这个资源设规则。在 Nacos 中新建一个配置文件Data ID 为gateway-service-sentinel-flowGroup 为SENTINEL_GROUP内容如下[ { resource: order-route, count: 5000, grade: 1, durationInSec: 1, limitApp: default }, { resource: product-route, count: 3000, grade: 1, durationInSec: 1, limitApp: default } ]这份配置的含义order-route路由每秒最多放行 5000 个请求product-route每秒最多放行 3000 个请求超过阈值的请求会被直接拦截返回Blocked by Sentinel: FlowException。3.2 核心参数逐个拆解这几个参数单独看都很简单但组合起来有很多讲究。我用一个表格把参数含义和注意事项写清楚参数含义注意事项resource限流资源名网关场景下填路由 ID 或 API 分组名count阈值单机维度下每台实例的阈值不是集群总量grade限流类型0 表示并发线程数1 表示 QPS默认用 1limitApp流控针对来源网关场景一般填 defaultdurationInSec统计时间窗口默认 1 秒配合 QPS 模式使用这里要特别强调一下count的含义。如果你是单机模式这个值就是每台机器每秒允许的 QPS。网关部署了 3 个实例每个实例配置的count是 3000那整个网关集群理论上能够放行的流量是 9000 QPS而不是 3000 QPS。这一点很多人在压测时才发现明明配置了 3000结果总 QPS 上到 8000 才触发限流原因就在这里。如果需要整个集群加起来不超过某个值就不能用单机模式的count而要用我后面第 5 部分讲的集群限流。3.3 自定义 API 分组实现更灵活的粒度按路由 ID 限流有个问题一个路由往往包含了多个不同优先级的接口。比如order-route里既有创建订单高优先级也有查询订单历史低优先级如果统一按路由限流两个接口互相争抢配额高优先级的接口也可能被低优先级流量挤掉。这时候可以用 Sentinel 的 API 分组功能把同一个路由里的接口按 URL 模式拆成更细的资源。在 Nacos 中新增网关 API 定义[ { apiName: order-create-api, predicateItems: [ { pattern: /api/order/create, matchStrategy: 0 } ] }, { apiName: order-query-api, predicateItems: [ { pattern: /api/order/query/**, matchStrategy: 0 } ] } ]然后再针对这两个 API 分组分别配置限流规则[ { resource: order-create-api, count: 2000, grade: 1 }, { resource: order-query-api, count: 1500, grade: 1 } ]matchStrategy为 0 表示精确匹配 URL 前缀模式/api/order/query/**匹配所有以该前缀开头的路径。用这种方式一个路由内部就可以按业务优先级做差异化限流效果比只按路由 ID 限流精细得多。3.4 限流效果验证规则配置好之后强烈建议先用 Jmeter 或 wrk 做一次小规模压测验证规则确实生效了。我在本地用 wrk 压过 5 秒钟的请求wrk -t4 -c100 -d5s http://localhost:8080/api/order/query/1命令行输出里会看到一个现象压测产生的 QPS 大概在 3000 左右但从 Sentinel Dashboard 的监控曲线可以看到实际通过的请求被限制在了 1500 附近其余请求返回了限流异常。如果你看到 Dashboard 上没有任何监控数据先不要怀疑规则配置大概率是客户端还没上报心跳。多访问几次接口或者检查spring.cloud.sentinel.eager是否配置为true。4. 联动服务层——从网关到服务的双保险4.1 下游服务接入 Sentinel 的方式网关层的限流只能挡住入口流量挡不住服务间调用。我上面说过订单服务调库存服务是不经过网关的。所以下游服务必须自己接入 Sentinel才能保护自己的核心资源。服务服务的接入方式比较简单引入依赖再加上 Annotation 注解即可dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-sentinel/artifactId /dependency在启动类或配置类中开启 Feign 的 Sentinel 支持feign: sentinel: enabled: true然后在需要限流的方法上加上注解SentinelResource(value create-order, blockHandler createOrderBlockHandler) public Order createOrder(OrderRequest request) { // 业务逻辑 }blockHandler是限流触发之后的兜底方法必须写在同一个类里方法签名要跟原方法保持一致只多加一个BlockException参数。除了注解方式我更推荐在 Nacos 里配置服务方的流控规则。因为注解只是定义资源规则不写在代码里才能实现动态调整。配置方式和网关类似只是rule-type要改成flow。4.2 联动时的阈值设计漏斗式收紧联动的核心是阈值怎么搭配。我采用的思路是入口宽、内部紧的漏斗结构层级资源阈值作用网关层order-route5000 QPS挡住大部分突发流量服务层create-order3000 QPS保护创建订单核心逻辑服务层order-query-api1500 QPS保护订单查询这一低频高耗接口为什么网关层阈值要比服务层高因为网关到服务是 1 对 1 转发网关放行的流量最终都会打到服务上。如果网关和服务阈值一样服务端的任何抖动都会直接导致网关限流提前触发等于把限流压力全集中在网关。反过来网关阈值高一些、服务阈值低一些服务端如果扛不住了限流会在更接近资源的地方发生保护效果更精准。这里有一个实战细节服务层限流的并发线程数模式往往比 QPS 模式更实用。QPS 模式只管每秒请求数但请求处理变慢的时候每秒 1000 个请求也可能把线程池打满。把grade设为 0、count设为线程池最大线程数的一半直接限制同时处理的请求数量对保护数据库连接池的效果非常明显。4.3 链路粒度的思考在高并发场景下还应该考虑链路限流。Sentinel 的链路是指资源之间的调用关系比如网关调用订单服务订单服务调用库存服务这是一条完整链路。联动限流和链路限流结合之后可以做很多精细的事情网关层拦截了 5000 QPS到了订单服务create-order又拦了一道往下调用库存服务时deduct-stock接口还可以再设一道防线。每一层都有兜底哪怕某一下游服务出现性能退化也不会让上游服务整个崩溃。这种多级限流的设计本质上是给系统加了几道不同位置的安全阀。我的习惯是核心链路至少三层防护入口一层业务服务一层资源访问最密集的 DAO 层或第三方调用层再补一层。5. 集群限流与高可用扩展5.1 网关集群场景下本地限流的缺陷在生产环境网关一定不会只部署一个实例。Nacos 注册中心里通常会注册两到三个网关实例前面再挂一层负载均衡SLB 或 Nginx。这种架构下就出现了一个问题每个 Sentinel 客户端的限流是单机维度的。比如你给order-route配置了 1000 QPS这是每台实例 1000 QPS。网关部署了 3 台总放行量实际上是 3000 QPS。如果你的预期是整个网关集群最多扛 1000 QPS那单机模式是无论如何都达不到这个效果的。有人可能会想到用 Redis 做统一计数但 Sentinel 集群限流的目的不是替代 Redis而是提供一个统一的 Token 服务端让集群内所有实例共享一套限流状态。这样 3 台网关的放行总量就能严格控制在配置的阈值内。5.2 集群限流原理与 Token Server 部署Sentinel 集群限流的机制不复杂集群中的一台机器作为 Token Server它负责统计集群维度的流量并分配令牌其他机器作为 Token Client每次请求限流的资源时先向 Token Server 申请令牌拿到令牌就放行拿不到就拦截。Token Server 有三种部署方式独立部署单独启动一个 Java 进程只跑 Token Server可用性最高但不适合资源紧张的小团队嵌入在某个服务中把 Token Server 嵌入网关或其中一个服务节省机器但该服务重启时 Token Server 会跟着挂独立进程 配置中心指定生产环境我建议用这种方式在网关项目中开启 Token Client 的配置spring: cloud: sentinel: cluster: client: server-host: 192.168.1.101 server-port: 11110对应地Token Server 的配置单独维护在一个配置类里Configuration public class ClusterServerConfig { PostConstruct public void init() { ClusterServerConfigManager.loadGlobalServerConfig( new ServerGlobalConfig() ); ClusterServerConfigManager.loadServerTransportConfig( new ServerTransportConfig(11110) ); } }配置完成后将集群限流规则通过 Dashboard 下发同时指定clusterMode: true。这个开关决定了规则是走单机统计还是集群统计。5.3 规则动态推送Nacos 持久化Sentinel 默认的规则是存在内存里的服务重启规则就丢了。生产环境必须接配置中心。我用的方案是 Nacos 数据源网关和服务各自通过spring.cloud.sentinel.datasource配置监听 Nacos 中的规则配置。每次规则变更的流程是这样的运维或开发在 Nacos 控制台修改 JSON 配置Nacos 推送变更到 Sentinel 客户端客户端自动更新内存中的规则整个过程不需要重启服务。这里有一个经验Dashboard 上虽然可以直接改规则但改完只存在内存里重启就丢。所以我的团队规定规则变更一律走 NacosDashboard 只看不写。否则就会出现线上改了一次数值重启后突然变回老规则的诡异问题。5.4 集群限流 vs Redis 令牌桶如果团队已经有比较成熟的 Redis 基础设施也有人会问直接用 Redis 实现令牌桶限流不行吗其实也可以两者的取舍我做过对比维度Sentinel 集群限流Redis 令牌桶限流统计精度高Token Server 直接计数高Redis 原子操作规则管理控制台、配置中心支持好需要自己写管理接口熔断降级支持不支持运维成本多一个 Token Server复用 Redis成本低与 Gateway 适配原生支持路由资源需要自己写 Lua 脚本如果你只需要单纯的流量控制Redis 令牌桶也能用。但微服务治理的场景下限流往往要和熔断、降级、热点参数防护一起考虑Sentinel 的整体方案会更顺手。6. 常见问题与排查技巧实录6.1 限流规则不生效从这三个方向排查网关限流规则配了压测却发现请求全部放行这是最常见的故障之一。我的排查顺序是先看 Sentinel 客户端是否真的加载了规则。登录 Dashboard找到网关服务打开流控规则页面看看刚才在 Nacos 里配的规则在不在。如果不在大概率是rule-type配置错了。记住网关规则是gw-flow服务规则是flow。再看资源名是否匹配。Gateway 的适配器对资源名的识别是严格区分的路由 ID 多一个空格、少一个字母都不行。你可以打开 Dashboard 的簇点链路页面查看实际上报的资源名长什么样子再去对齐 Nacos 里的规则配置。最后看版本兼容性。sentinel-spring-cloud-gateway-adapter的版本必须和spring-cloud-starter-alibaba-sentinel内的 Sentinel 核心版本一致。不一致的时候表现非常迷惑启动不报错、请求也正常但限流规则就是默默不生效。我的建议是统一锁定到 1.8.6 版本。6.2 控制台找不到应用或显示 H0007 类错误网关启动之后Sentinel Dashboard 的机器列表里一直看不到应用或者在接入过程中报 H0007 之类的客户端连接异常这种情况一般是客户端向控制台注册失败。第一步检查spring.cloud.sentinel.transport.dashboard地址是否可以从网关所在机器访问telnet 一下控制台端口telnet 192.168.1.100 8858第二步检查transport.port是否有冲突。Sentinel 客户端默认监听 8719 端口用于给控制台上报心跳。如果多实例部署在同一台机器或者端口被占用需要显式指定不同的端口避免互相抢占。第三步检查eager配置。不配置eager: true的做法下Sentinel 要等第一次请求链路才会初始化客户端注册。在压测场景中如果压测请求在初始化之前就已经打完了你会在控制台上错过应用注册的窗口期。把eager打开可以规避这个问题。6.3 集群限流模式下 Token Server 挂了的处理集群限流模式下如果 Token Server 进程挂了所有 Token Client 会降级到本地限流模式。这个设计是有意为之的目的是保证 Token Server 故障时系统还能继续运转而不是直接把整个流量拦住。但要注意降级到本地模式后集群的限流总量会失去约束。每一台 Gateway 都会按自己本地的配额放行总流量可能瞬间超标。我的处理办法是在 Token Server 所在节点配置监控告警一旦进程消失立刻告警。同时在网关侧用spring.cloud.sentinel.cluster.client.fallback-to-local-threshold配置本地降级阈值让降级后的本地阈值比正常集群阈值低一些尽量在降级期间也不打满下游。6.4 规则配置在 Nacos 里但重启后丢失这个问题我至少被问过十次。规则确实在 Nacos 里服务的datasource配置也没问题但服务重启后规则还是丢了。排查思路是这样的Nacos 数据源会在服务启动时拉取一次配置并建立长连接监听后续变更。如果网关启动时 Nacos 里的配置还没有创建好数据源会拉取到空数据。之后你在 Nacos 里新增了配置服务端确实能收到变更通知但有些版本的适配器对空配置到有配置的推送监听存在兼容问题导致新规则没有加载到内存。解决办法也很简单先把 Nacos 里的规则配置准备好再启动服务。不要图方便先启动服务后建配置。配置中心的变更推送在生产是可靠的但启动顺序这种低级问题我们还是不要依赖推送机制去兜底。最后的几句实在话方案跑通之后我个人最大的体会是限流规则永远不要凭感觉定数值。刚刚落地那阵子我习惯把阈值拍脑袋填一个好看的整数结果不是限流误伤正常用户就是阈值太宽等于没限。后来改成压测定基线、线上再微调的路子先用低阈值压测确认链路和规则都生效再逐步放开观察监控曲线找到拐点最后把阈值定在拐点的八成左右。另外想分享一个小技巧在 Nacos 里配置规则时JSON 尽量加上注释字段。比如desc: 大促期间临时提额活动结束后改回。这个字段规则本身不识别但能让后面接手的人一眼看懂这条规则为什么存在。限流这种和线上流量直接相关的配置最怕的不是配错而是配了没人知道为什么这么配。这套 Sentinel 加 Gateway 的联动方案后续还可以继续扩展接入自定义的RequestOriginParser实现按用户维度限流或者结合 Prometheus 做更细粒度的监控告警。限流不是一锤子买卖它是一个持续跟着业务流量走的动态过程。
返回列表