先说一个很多团队都会踩的场景:网关做了 QPS 限流,依然打挂了下游服务。这个场景用阿里巴巴开源的 Sentinel 来做流量治理,会比只在网关写死一个阈值可靠得多。
我理解 Sentinel 是一个以“资源”为圆心的流量治理框架,把限流、熔断、热点参数保护和系统自适应保护统一到一套规则模型里,再配合 Dashboard 做可视化的规则观察与运维。它不是单纯“拦请求”的工具,更合适的定位是给系统留出可控的呼吸余地。这篇文章适合正在做微服务拆分、对接口稳定性有要求、想把限流规则从代码里解放出来的人;下面这些经验,更多是我在网关、订单服务和压测环境中反复调出来的体感,不是官方文档的复述。
1. 为什么流量治理会从“加机器”变成“精细控流”
1.1 一次压测事故给我的教训
之前我们团队的服务架构不算复杂:一个网关,后面挂了四个订单服务实例。平时 QPS 不高,限流规则只放在网关层,服务端几乎没有兜底。结果一次压测从 1000 QPS 开始跑,网关看着还挺正常,但订单服务 CPU 直接到 90%,平均响应时间从 80ms 一路涨到 2300ms,最后健康检查失败,实例被摘掉,流量又倾斜到剩余实例上,整体雪崩。
这个事故让我意识到:网关限流是粗粒度入口控制,它只能保证“进到系统里的流量不超标”,不能保证“每个下游服务都被保护到”。订单服务依赖数据库连接、外部库存接口、Redis 缓存,任何一条链路都可能成为瓶颈。如果只把阈值放在网关,服务端完全裸奔,那等于把稳定性押在单一节点上。
1.2 突刺流量才是常态
线上流量不是压测那样的匀速曲线。秒杀、运营活动、定时任务集中触发、爬虫、重试风暴,都会带来大量瞬时的流量尖刺。如果规则是静态写死的,要么阈值配高了挡不住,要么阈值配低了误伤正常用户。
Sentinel 解决这类问题的思路是:把规则和业务代码解耦,规则可以动态调整,同时在一个资源上叠加不同类型的保护。比如接口 QPS 超了快速失败,RT 持续变高自动熔断,热点参数被刷时单独限制,系统整体负载高了再兜底限流。它不是某一个功能的堆叠,而是一套组合拳。
1.3 选择 Sentinel 的三个理由
- 规则动态化:不需要改代码重启服务就能调整阈值。配合 Nacos、Apollo 这类配置中心,还能做成规则下发平台。
- 保护维度全:支持 QPS、并发线程数、响应时间、异常比例、热点参数、系统负载等不同维度的规则,覆盖了绝大多数稳定性场景。
- 社区和生态成熟:和 Spring Cloud Gateway、Spring Cloud Alibaba、Dubbo 都有适配,接入成本比从零自研低很多。
如果你之前用过 Hystrix,应该知道 Hystrix 偏向线程池隔离,规则基本是静态配置,社区也基本停止维护。Sentinel 的做法更轻量,默认用信号量 + 计数器模式,额外开销小很多。
2. Sentinel 的基础模型:资源、规则、Slot 链与统计窗口
2.1 资源是切入点
Sentinel 里的“资源”可以理解成你想保护的一个入口。它可以是一个 URL、一个 Dubbo 接口、一个方法,甚至是一条 SQL。接入时,埋点位置决定了你能挡住什么。
在 Spring 工程里,最常用的埋点方式是@SentinelResource注解:
@SentinelResource(value = "createOrder", blockHandler = "createOrderBlock", fallback = "createOrderFallback") public Order createOrder(OrderForm form) { return orderService.create(form); }blockHandler是在触发限流、熔断等规则时走的逻辑,fallback是业务方法抛出异常时走的兜底。很多同学会把这两个搞混:前者是 Sentinel 在入口处拦截,后者是异常处理,两者负责的阶段不一样。
有一点要特别注意:@SentinelResource注解需要引入sentinel-annotation-aspectj依赖并开启 Spring AOP 切面,否则注解不会生效。我见过有人配了注解但完全没有规则,以为限流生效了,等到压测才发现流量根本没被统计。
2.2 规则与效果器
规则描述的是“什么条件下触发保护”,效果器描述的是“触发之后怎么做”。Sentinel 默认支持快速失败、Warm Up 预热、匀速排队三种流控效果,熔断后也可以选择快速失败或走 fallback。
| 规则类型 | 判断维度 | 典型效果 |
|---|---|---|
| FlowRule | QPS、并发线程数 | 快速失败、Warm Up、匀速排队 |
| DegradeRule | RT、异常比例、异常数 | 熔断降级、快速失败 |
| ParamFlowRule | 热点参数 QPS | 精准拦截参数维度超卖 |
| SystemRule | 系统负载、CPU、入口 QPS | 全局自适应保护 |
我通常建议先把 FlowRule 和 DegradeRule 配上,这是保命的基本盘;热点参数和系统规则属于进阶项,根据实际业务需要再叠加。
2.3 Slot 链与统计窗口
Sentinel 统计的底层不是简单计数器,而是一个叫 LeapArray 的滑动窗口结构。固定窗口的问题是:59 秒的时候放进来 1 万请求,00 秒的时候又放进来 1 万请求,单独看每个窗口都没超阈值,但瞬时压力其实已经翻倍。
滑动窗口把 1 秒切成多个小格子,默认切成 2 个 500ms 的窗口,随时间推进不断滑动。这样哪怕请求都挤在窗口边界,也不会被漏掉。统计越精细, CPU 开销也会越高,所以默认切 2 格是性能和精度之间比较均衡的选择。
Slot Chain 是规则执行的链条,比如在一个请求进来时,会依次经过流控 Slot、熔断 Slot、热点 Slot、系统保护 Slot。每个 Slot 只负责自己的检查,谁触发谁就返回 BlockException。理解这个链条的意义在于:当你看到请求被拦截时,要先定位是哪一个 Slot 的问题,而不是瞎调一个阈值。
3. Dashboard 部署与客户端接入:从可视化到规则下发的边界
3.1 本地启动 Dashboard
Sentinel Dashboard 是一个独立的 Java 应用,下载官方 release 包后,可以直接 jar 启动:
java -Dserver.port=8080 \ -Dcsp.sentinel.dashboard.server=localhost:8080 \ -Dproject.name=sentinel-dashboard \ -jar sentinel-dashboard-1.8.6.jar启动完成后,浏览器访问http://localhost:8080,默认账号密码是sentinel/sentinel。刚登录时机器列表是空的,因为客户端要主动上报心跳,Dashboard 才会感知到应用。
这里有个细节:-Dcsp.sentinel.dashboard.server这个参数对 Dashboard 本身也要配,否则 Dashboard 自己上报的数据会串。客户端的-Dproject.name表示应用在控制台里的名字,一个应用多个实例建议用同一个 project name,方便看集群维度数据。
3.2 客户端接入与参数语义
如果项目没有用 Spring Cloud Alibaba,需要手动加两个依赖:
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.8.6</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>1.8.6</version> </dependency>然后在 JVM 启动参数里加上:
-Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=order-service -Dcsp.sentinel.api.port=87198719是客户端暴露的本地 API 端口,Dashboard 会通过这个端口拉取规则和统计数据。如果被占用,Sentinel 会自动尝试下一个端口,但日志里会显示实际使用的端口。
接入后,日志里能看到类似[Sentinel] log output path的信息,说明客户端已经正常初始化。第一次调用被保护的资源后,Dashboard 上才会看到对应资源名,这是因为 Sentinel 是懒加载统计模型,没有流量就不会上报。
3.3 规则持久化为什么不能直接依赖控制台
官方 Dashboard 可以把规则下发到客户端,但规则只存在客户端内存和控制台内存里。应用重启,规则没了;控制台重启,规则也没了。所以它适合做联调、观察和临时验证,不适合当生产环境的唯一配置源。
生产环境的规则一定要落到配置中心,比如 Nacos、Apollo、Redis。后面我专门讲推拉模式,这一节只需要记住:别把生产规则直接放在 Dashboard 里手工点。
4. 规则配置与效果器:限流、熔断、热点、系统保护怎么选
4.1 FlowRule 配置例子
限流规则是最常用的。以“创建订单”接口为例,先定义资源名,再通过 FlowRuleManager 加载规则:
FlowRule rule = new FlowRule("createOrder"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(200); rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP); rule.setWarmUpPeriodSec(10); FlowRuleManager.loadRules(Collections.singletonList(rule));这里的关键参数是grade和count。FLOW_GRADE_QPS表示每秒最多通过 200 个请求;FLOW_GRADE_THREAD表示并发线程数超过 200 时直接拒绝。线程数维度适合保护那些执行时间不稳定、容易堆积线程的任务。
CONTROL_BEHAVIOR_WARM_UP是我个人比较喜欢的效果器。它让流量从较低阈值缓慢爬升到目标值,避免刚启动的服务被大量流量直接打崩。Warm Up 尤其适合缓存还没预热、连接池还没建立完整的场景。
4.2 熔断降级规则
限流解决的是“来太多”,熔断解决的是“下游不行了”。当被调用的服务 RT 持续升高,或者异常比例超过阈值,就不应该再继续打进去,而是快速失败或走 fallback。
DegradeRule rule = new DegradeRule("createOrder"); rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); rule.setCount(300); rule.setTimeWindow(10); DegradeRuleManager.loadRules(Collections.singletonList(rule));这个例子表示:如果 createOrder 的平均响应时间超过 300ms,持续一段时间后触发熔断,熔断窗口为 10 秒。10 秒后进入半开状态,放少量请求试探恢复情况。
很多人只关注异常熔断,却忽视了 RT 熔断。实际上高延迟对用户体感的伤害不亚于报错,而且延迟会引起线程堆积,最终拖垮整个服务。所以核心链路上,RT 熔断和异常熔断我都建议同时配。
4.3 热点参数限流
热点参数限流解决的是“某个具体参数被刷”的问题。整体 QPS 不高,但同一个商品 ID 被反复查询,数据库扛不住;或者某个用户 ID 在疯狂重试,拖慢全局。
ParamFlowRule hotRule = new ParamFlowRule("queryStock"); hotRule.setParamIdx(0); hotRule.setCount(100); ParamFlowItem item = new ParamFlowItem(); item.setObject("商品ID=10086"); item.setCount(10); hotRule.setParamFlowItemList(Collections.singletonList(item)); ParamFlowRuleManager.loadRules(Collections.singletonList(hotRule));paramIdx表示资源埋点方法的第几个参数作为热点维度。默认所有参数值共享count的阈值,如果某个特定值命中ParamFlowItem,就走它自己的阈值。
这里要提醒:热点参数限流依赖入参来源,如果用 Spring Cloud Gateway 做接入,参数可能不在方法参数上,而是在 HTTP Header 或 URL 参数里,需要额外适配。不要想当然认为一个注解就能覆盖所有场景。
4.4 系统保护规则
系统规则是兜底策略,它不针对某个接口,而是根据当前系统的整体负载来自动调整入口流量。比如系统 Load 超过阈值,就拒绝多余的入口流量。
SystemRule rule = new SystemRule(); rule.setHighestSystemLoad(10); rule.setAvgRt(500); SystemRuleManager.loadRules(Collections.singletonList(rule));这类规则适合放在核心服务入口,作为最后一道防线。但不要把系统规则当常规限流用,因为它的判断周期和粒度都比较粗,不适合精确控制某个接口。
5. Spring Cloud Gateway 接入 Sentinel:路由、API 分组和避坑记录
5.1 网关限流的特殊位置
网关是所有流量的必经之路,非常适合做第一层限流。但网关限流不能替代服务端限流,因为网关并不知道下游每个实例的连接池容量、缓存命中率和数据库压力。
我见过的正确姿势是:网关层按路由维度限制总入口流量,服务端按核心接口维度做精细化限流。两边阈值要有层级关系,比如网关给订单路由限 500 QPS,订单服务核心接口再限 200 QPS,防止网关放行过多后服务端被打穿。
5.2 基础依赖与配置
使用 Spring Cloud Gateway 时,Sentinel 提供了专门的适配包。需要引入:
com.alibaba.csp:sentinel-spring-cloud-gateway-adaptercom.alibaba.csp:sentinel-transport-simple-httpcom.alibaba.csp:sentinel-annotation-aspectj
然后注册两个核心 Bean:
@Configuration public class GatewaySentinelConfig { @Bean public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { return new SentinelGatewayBlockExceptionHandler(); } @Bean public SentinelGatewayFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } }SentinelGatewayFilter负责在请求链路中创建 Sentinel 统计入口;SentinelGatewayBlockExceptionHandler负责在触发限流时返回统一的错误响应。如果 filter 注册顺序不对,限流效果会很奇怪,建议在配置类里显式控制顺序。
5.3 路由维度 vs API 维度规则
网关限流有两类资源粒度:
- 路由维度:以
routeId作为资源名,一个路由一条规则。 - API 分组维度:把多个路径聚合到一个 API 定义下,比如
/api/order/**和/order/**都算同一个资源。
Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("order-service") .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID) .setCount(500) .setIntervalSec(1)); GatewayRuleManager.loadRules(rules);很多人在刚开始接入时有个误区:以为资源名是 URL 路径。实际上路由维度的资源名是routeId,也就是你在 Spring Cloud Gateway 配置里给路由起的名字。如果规则配成了/order/**,大概率不生效,因为 Sentinel 在网关层拿到的资源名并不是路径。
API 分组解决的是更灵活的场景。比如你有一个/api/payment/**的路由,但只想对其中涉及支付的部分路径做更严格限制,用 API 分组可以把这些路径聚合成一个独立资源进行限流。
5.4 实际踩过的坑
接入网关限流后,我遇到过几个比较典型的坑,这里分享出来:
第一,Block 响应格式太简陋。默认的兜底响应只包含错误信息,前端根本分不清是限流还是后端故障。建议通过GatewayCallbackManager.setBlockHandler自定义返回 JSON,包含业务错误码、提示信息和触发规则类型,这样排查问题会快很多。
第二,规则生效有延迟。网关适配器底层也是从本地规则仓库取规则,如果使用loadRules加载,修改后需要全量覆盖,且多个网关实例不会自动同步。后来把规则数据源接到 Nacos,才真正解决多实例一致性问题。
第三,和全局过滤器执行顺序冲突。如果项目里自定义了全局过滤器,且执行顺序在 SentinelGatewayFilter 之前,那么一些请求可能绕过统计。建议把 SentinelGatewayFilter 的 order 调小,尽量早执行,避免漏统计。
6. 规则同步的推拉模式:生产环境最终形态与稳定性核查点
6.1 原生模式为什么不可取
原生模式下,每个客户端通过loadRules()本地加载规则,规则只存在于各自内存。控制台修改规则时,它会把规则推给对应机器,但机器重启后规则丢失;同时多实例场景下,A 实例改了规则,B、C 实例不知道,流量稍大就会出现“一半实例在限流,一半实例裸奔”的诡异状况。
有一次我们调整订单超时熔断阈值,只在一个实例上完成了验证,另一个实例还是老规则,结果线上流量随机路由,老实例继续报错,排查了很久才发现是规则不同步。从那以后我坚持一个原则:生产环境的规则必须统一收敛到配置中心。
6.2 拉模式与推模式的取舍
在生产环境中,规则同步方式主要分两种:
| 模式 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 拉模式 | 客户端定时从文件、DB、配置中心拉取规则 | 实现简单,依赖少 | 实时性差,故障恢复慢 |
| 推模式 | 配置中心主动通知客户端变更 | 实时性好,规则统一管理 | 需要改造数据源,接入成本高 |
我推荐用推模式。Sentinel 客户端本身提供了多种 DataSource,比如NacosDataSource、ApolloDataSource、RedisDataSource,只要把规则数据源注册到对应的 RuleManager,配置中心一变,客户端就会自动更新。
还要记住一个重要概念:这些规则都是单机维度的。三个网关实例都配置 100 QPS,系统总入口其实是 300 QPS。如果要求全局精确限流,还需要引入 Sentinel 的 ClusterFlow 模式,或者提前按实例数拆分每台阈值。大多数业务场景下,按单机阈值控制已经够用,但架构评审时要心里有数。
6.3 以 Nacos 为配置中心的推送方案
先引入依赖:
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.8.6</version> </dependency>注册数据源:
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>( "localhost:8848", "DEFAULT_GROUP", "sentinel-flow-rules", source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}) ); FlowRuleManager.register2Property(flowRuleDataSource.getProperty());这里的关键是sentinel-flow-rules这个 dataId,里面的内容是一段 JSON 数组。在 Nacos 控制台维护好这段配置后,客户端会监听变更并实时更新内存规则。
生产环境上,可以把 Dashboard 的规则发布能力改造为基础版本发布到 Nacos,或者直接用 Nacos 控制台作为规则管理入口。控制台主要用于观察实时流量和查看资源拓扑,规则配置的工作一般不必依赖控制台完成。
6.4 稳定运行的核查点
把规则放到配置中心之后,还要做好几个核查点:
- 环境隔离:不同环境用不同的 Nacos namespace 或 group,避免测试环境规则污染生产。
- 变更审计:配置中心自带历史版本,规则变更要通过配置中心操作记录,方便回溯。
- 格式校验:规则 JSON 发布前要校验字段类型和枚举值,格式错误会导致客户端解析失败。
- 压测验证:每次调整规则后,至少压一轮核心接口,确认新阈值不会误杀正常流量。
有一点我最开始在实践里吃过亏:规则变更后,虽然客户端刷新了,但并没有一个通用机制告诉我们“哪些资源用了哪些规则”。我建议在接入初期就把资源命名规范定好,比如统一使用接口路径或业务语义清晰的名称,否则后面资源多了,规则和资源的对应关系会非常混乱。
如果你也刚开始接 Sentinel,先把 Dashboard 跑起来,在网关和一个核心服务上各配一条 QPS 规则,把从埋点到控制台观察的链路走通,再考虑接 Nacos 做规则持久化。别一上来就追求大而全的规则平台,流量治理本质上是一个持续迭代、逐步体系化的过程。