1. 微服务链路雪崩:Sentinel要解决的那个实际问题
1.1 一次线上雪崩的形成过程
先讲一个真实场景。我之前维护过一个订单服务,核心链路上要调用库存服务。平时库存服务响应很快,大概20ms,订单服务用默认的200个Tomcat线程完全够用。某天库存服务背后的数据库出现了一条慢查询,接口RT从20ms直接涨到2秒。这时候订单服务的200个线程里,每一个请求都要占用2秒才能释放,1秒钟最多处理100个请求,而系统入口QPS是200。请求开始堆积,Tomcat线程池被打满,后面的请求全部排队等线程。问题到这里还不算完,订单服务超时后,上游支付服务又开始重试,流量被再次放大,最终整条链路上的服务全部不可用。
这就是微服务里最典型的雪崩:一个下游节点变慢,通过线程堆积和超时重试逐级传染,最终拖垮整个调用链。熔断与限流要解决的就是这个问题。SpringCloud生态里,Sentinel就是专门做这件事的组件——限流保护自己,熔断保护调用关系,降级提供兜底。这篇就沿着我自己接入Sentinel的完整过程,把熔断和限流从原理到落地一步步讲清楚。
1.2 限流、熔断、降级三者的边界
很多初学者容易把这三个概念混在一起,实际它们的分工完全不同。
限流是控制进入系统的流量,保护的是自身处理能力。比如一个订单接口只能扛100 QPS,那超过的请求直接拒绝或排队,不让系统被打垮。
熔断是调用下游失败时的一种快速失败机制。当下游接口连续出错或响应过慢,触发熔断后,调用方会在一段时间内不再真正请求下游,直接返回错误或走兜底逻辑。熔断保护的是调用关系,避免故障蔓延。
降级则是当系统压力过大或者依赖不可用的时候,主动放弃一些非核心功能,返回一个默认值或缓存数据,保证主流程还能跑通。降级是结果处理策略,熔断是触发条件之一。
打个比方:限流是小区门口的门禁,控制每小时进去多少人;熔断是发现电梯坏了立刻贴出停运公告,不让大家继续按按钮;降级是电梯坏了以后,引导大家走楼梯或者安排摆渡车,反正保证你还能到家。
1.3 为什么这个时代选Sentinel而不是继续用Hystrix
Hystrix在微服务早期几乎是熔断降级的代名词,但它的维护状态已经持续很久了。更关键的是,Hystrix的线程池隔离机制开销很大,每个被保护的下游服务都要单独分配一组线程池,线程上下文切换的成本在高并发场景下非常扎眼。它的Dashboard控制台也比较基础,想在上面直接调整规则几乎不可能。
Sentinel的思路完全不同:它基于滑动窗口做实时统计,不搞线程池隔离,资源开销小得多。默认情况下,一个方法、一个URL、一个Feign调用都能自动成为被保护资源,不需要手动包一层线程池。再加上Sentinel提供了功能完整的控制台,可以实时看到每个接口的QPS、RT、Blocked数量,直接在界面上修改限流阈值和熔断规则,这是Hystrix时代不敢想的体验。
当然选型还要看团队技术栈。如果你用的是Spring Cloud Alibaba这套体系,注册中心是Nacos,那Sentinel基本是顺理成章的选择,因为它和Nacos、OpenFeign、Gateway的整合非常顺滑,后面我会专门讲和OpenFeign配合时的配置。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Spring Cloud Alibaba环境准备:版本匹配与控制台启动
2.1 版本对应关系:最容易翻车的第一步
SpringCloud Alibaba对版本极其敏感,很多人项目跑不起来,十有八九是版本对不上。落地Sentinel之前,先把BOM版本理清楚。
我整理了一张常用的版本对应表,注意以官方发布为准:
| Spring Cloud Alibaba Version | Spring Cloud Version | Spring Boot Version | Sentinel Version |
|---|---|---|---|
| 2.2.x.RELEASE | Hoxton.SR12 | 2.3.x.RELEASE | 1.8.x |
| 2021.0.5.0 | 2021.0.5 | 2.6.13 | 1.8.6 |
| 2022.0.0.0 | 2022.0.0 | 3.0.x | 1.8.6 |
| 2023.0.1.0 | 2023.0.1 | 3.2.x | 1.8.8 |
我自己生产环境用的是2021.0.5.0这一档,Spring Boot 2.6.13,Sentinel 1.8.6,跑了很久没有出现兼容性问题。如果你是新项目,优先选择官方推荐的最新稳定组合,不要瞎配版本。
引入依赖时,不要手动加版本号,统一用BOM管理:
xml复制<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.5.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后在具体的模块里引入Sentinel starter:
xml复制<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.2 客户端基础配置
在application.yml里加上Sentinel的配置:
yaml复制spring:
application:
name: order-service
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8719
eager: true
web-context-unify: false
这里有一个新手经常忽略的点:transport.port是客户端和控制台通信的端口,默认8719。如果这个端口被其他进程占用了,Sentinel会自动向后顺延一个端口,也就是8720、8721这样。这个特性看似智能,但如果防火墙只放行了8719,客户端端口顺延后控制台仍然能通过心跳看到,但反向通信就可能失败。所以生产环境最好固定端口并且同步到安全组规则里。
还有一个eager: true,它的作用是让客户端在应用启动时就主动注册到控制台,而不是等第一次网络请求进来。没有这个配置,你启动服务后会发现在控制台看不到任何服务实例,这也是下面要说的懒加载陷阱。
2.3 控制台启动与登录
Sentinel的控制台是一个单独的Jar包,从官方GitHub的release页面下载对应版本。下载后直接启动:
bash复制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。登录后你会看到一个空白的首页,这时候不要怀疑控制台坏了,是因为还没有服务接入。
2.4 首次接入的"懒加载"陷阱
Sentinel的客户端接入控制台后,如果一直没有实际流量,控制台的"机器列表"里就是空的。这个坑我踩过一次,当时怎么查都是配置问题,最后发现只是没触发请求而已。
解决方式有两个:要么配置eager: true,要么启动后随便访问一个接口。我推荐前者,一劳永逸。但要注意,eager: true只能保证客户端上报心跳,如果你希望控制台能看到某个接口的监控数据,仍然需要真实调用一次接口。Sentinel的埋点机制是访问到哪个资源,才给哪个资源建立统计节点,这是懒加载设计,和客户端注册是两回事。
3. 资源与规则:理解Sentinel的执行链路
3.1 资源是什么
Sentinel里的"资源"是一个核心概念,它可以是一个方法、一个URL、一段代码逻辑。规则都是绑定在资源上的,没有资源名,规则就无法生效。
接入Sentinel有几种方式。最原始的是用SphU.entry()手动埋点:
java复制try (Entry entry = SphU.entry("createOrder")) {
return orderService.createOrder(orderDTO);
} catch (BlockException ex) {
return Result.fail("当前请求量过大,请稍后再试");
}
这样createOrder就是一个资源。但手动埋点侵入性还是有的,更常用的是@SentinelResource注解:
java复制@SentinelResource(value = "createOrder", blockHandler = "createOrderBlock")
public String createOrder(OrderDTO orderDTO) {
return orderService.createOrder(orderDTO);
}
如果你用的是Spring MVC,Sentinel的Web适配器还会自动把URL注册成资源。也就是说,你什么都不用做,/order/create这个URL就已经是一个可用于配置规则的资源了。这也是为什么很多人接入Sentinel后,第一反应是"什么都没写居然控制台就有数据了"。
3.2 Sentinel的ProcessorSlotChain执行链路
Sentinel之所以能做到低侵入的实时统计和规则判断,核心是它有一条责任链,叫ProcessorSlotChain。每个资源通过时会依次经过这些Slot,我按官方默认顺序说一下:
NodeSelectorSlot -> ClusterBuilderSlot -> LogSlot -> StatisticSlot -> AuthoritySlot -> SystemSlot -> FlowSlot -> DegradeSlot
NodeSelectorSlot负责维护调用链路上每个资源对应的节点,ClusterBuilderSlot负责生成集群维度统计节点,StatisticSlot负责实时写入滑动窗口统计数据,AuthoritySlot做黑白名单授权,SystemSlot做系统自适应保护,FlowSlot做流量控制,DegradeSlot做熔断降级。
这条链对性能的影响很小,因为大部分Slot只是做一些内存计数和判断。理解这条链有什么好处?当你在排查"为什么接口被限流了"的时候,至少能知道判断顺序:先看授权规则,再看系统规则,然后才看流控和熔断。实际排查中,很多人熔断和流控规则都没配置,接口却被拦截,最后发现是系统保护规则里的CPU阈值被触发了,这就是不了解SystemSlot位置的后果。
3.3 规则的种类与加载方式
Sentinel支持五类规则:流控规则、熔断降级规则、热点参数规则、系统保护规则、授权规则。这些规则可以通过三种途径加载:
第一种是用代码注册,比如FlowRuleManager.loadRules(),适合在启动时初始化一些固定规则。
第二种是在控制台上配置,好处是可视化、实时生效,坏处是规则只存在控制台内存和客户端内存里,重启就丢。
第三种是通过数据源动态加载,比如从Nacos、Zookeeper、Apollo配置中心读取,这也是生产环境最推荐的方案。
后面讲规则持久化时我会展开第三种方式,现在先聚焦在流控和熔断这两大块。
4. 流量控制实战:从阈值到流控算法的完整落地
4.1 阈值类型:QPS还是并发线程数,怎么选
流控规则创建时,第一个要选的就是阈值类型。Sentinel提供了两种:QPS和并发线程数。
QPS就是每秒允许通过的请求数量,直观、好理解。但有一个问题:它不区分请求耗时。假设一个接口平均耗时500ms,单机最多同时处理40个请求(按20个线程算),如果你把QPS阈值设成100,其实已经超过了真实处理能力,大量请求还是会排队。
并发线程数限制的是"同时占用的线程数",更贴近线程池保护的场景。对于耗时较长的接口,比如导出报表、批量计算,用并发线程数做阈值更合理,因为它直接卡住了资源占用。
我的判断标准是:接口耗时在100ms以内,用QPS;超过100ms,用并发线程数。当然实际还要结合压测数据,不能拍脑袋。
4.2 流控模式:直接、关联、链路
流控模式有三种,很多人只用了"直接",另外两种其实非常有用。
直接模式就是对当前资源本身限流,超过阈值就拦截,没什么好说的。
关联模式是指:当关联资源达到阈值时,对当前资源限流。典型场景是数据库读写互相影响。比如一个详情页,读接口/order/detail和写接口/order/create共用一张表,读流量飙升时会把数据库连接池占满,导致写接口也失败。这时候可以给写接口配置关联模式,关联资源指向读接口,当读接口QPS超过阈值时,写接口主动限流,保证核心的写链路不受冲击。
链路模式更细致,它根据调用入口来区分流量。比如getProduct这个方法同时被createOrder和queryCart两个入口调用,如果只希望限制"从createOrder入口进来的流量",就可以配置链路模式,入口资源填createOrder。要注意的是,使用链路模式必须设置spring.cloud.sentinel.web-context-unify: false,否则Spring MVC会把所有URL统一成一个全局context,链路入口就区分不出来了。
4.3 流控效果:快速失败、Warm Up、排队等待
流控效果决定了触发限流后的表现,这部分也是面试里特别爱问的。
快速失败是默认效果,一旦超过阈值直接报BlockException,最简单粗暴,适合对延迟敏感、宁可拒绝也不愿意等的接口。
Warm Up是冷启动模式。刚启动时系统内部的缓存、数据库连接池都是空的,如果直接把流量打满,系统反而容易被打垮。Warm Up默认会以阈值的1/3作为初始阈值,然后在预热时间内平滑上升到设置的目标阈值。底层实现是令牌桶算法。比如设置了100 QPS,预热时间10秒,那前几秒实际允许的QPS只有33左右,慢慢爬升到100。上线新版本或重启后流量突增的场景,用Warm Up特别合适。
排队等待对应的是匀速排队,底层是漏桶思想。请求先进入队列匀速通过,超过超时时间还在排队的请求直接失败。它能把突发的脉冲流量削平,适合需要控制下游压力的场景。配置时除了阈值还要设置最大排队超时时间maxQueueingTimeMs:
java复制FlowRule rule = new FlowRule();
rule.setResource("createOrder");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(100);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
rule.setMaxQueueingTimeMs(500);
FlowRuleManager.loadRules(Collections.singletonList(rule));
这里有个容易忽视的细节:QPS为100时,排队模式下每个请求的间隔是10ms,如果某个请求在队列里等了超过500ms还没轮到,它就会被直接拒掉。
4.4 一个订单接口的限流配置示例
拿我实际的项目举例,订单创建接口压测峰值大概是120 QPS,我没有直接把阈值设成120,而是设成100,留20%余量,避免突发流量把系统打到极限。考虑到接口内部依赖库存服务和数据库,第一次冷启动时连接池还没热起来,我用了Warm Up模式:
java复制FlowRule rule = new FlowRule();
rule.setResource("createOrder");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(100);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
rule.setWarmUpPeriodSec(30);
FlowRuleManager.loadRules(Collections.singletonList(rule));
30秒的预热时长是按数据库连接池的初始化耗时估的,如果项目里还挂了缓存,预热时间可以延长到60秒。但如果你发现接口响应本身就慢,Warm Up期间大量请求积压,那就需要缩短预热时间或者直接换快速失败。
4.5 顺带说说Redis实现令牌桶限流的取舍
技术群里经常有人讨论"既然有Redis,为什么还要用Sentinel"。Redis+Lua确实可以实现令牌桶限流,特别是做全局限流的时候比较顺手。但落地时你会发现,令牌桶的桶大小、速率、Lua脚本维护、监控报警都要自己写,而且每个接口都要手动埋点,改一个限流阈值要发一次代码。
Sentinel的价值在于:埋点自动化,规则热更新,实时监控开箱即用,还能做熔断降级。如果你的项目已经引入Redis,且只对极少数接口做限流,用Redis令牌桶也完全没问题。但如果你是做整个微服务体系的治理,Sentinel更省心。两种方案不是互斥的,网关层的全局限流用Redis令牌桶,服务内的接口限流用Sentinel,是实践中很常见的组合。
5. 熔断降级实战:三条规则与状态机
5.1 慢调用比例、异常比例、异常数
熔断降级规则有三种触发方式,对应三种不同的故障特征。
慢调用比例:统计一段时间内,RT超过指定阈值的请求占比。比如设置最大RT为200ms,统计时长1秒,最小请求数为5,慢调用比例阈值为0.5。意思是1秒内至少要有5个请求,其中超过一半的请求RT大于200ms,就触发熔断。这个规则对"下游变慢但不报错"的场景非常有效。
异常比例:统计时间窗口内异常请求的比例。适合下游直接抛错的场景,比如数据库连接失败、接口返回500。我一般设置异常比例0.2,也就是20%的请求出错就熔断。
异常数:统计时间窗口内异常请求的绝对数量。适合本身请求量不大,但异常开始密集出现的场景。比如一个管理后台接口,平时一分钟只有10次调用,有4次异常,按比例看已经40%了,但绝对量很小,用异常比例可能来回抖动;用异常数更稳定。
三种规则的代码配置我都写出来:
java复制// 慢调用比例
DegradeRule rtRule = new DegradeRule("getStock");
rtRule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rtRule.setCount(200);
rtRule.setSlowRatioThreshold(0.5);
rtRule.setMinRequestAmount(5);
rtRule.setStatIntervalMs(1000);
rtRule.setTimeWindow(10);
// 异常比例
DegradeRule ratioRule = new DegradeRule("getStock");
ratioRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
ratioRule.setCount(0.2);
ratioRule.setMinRequestAmount(5);
ratioRule.setStatIntervalMs(1000);
ratioRule.setTimeWindow(10);
// 异常数
DegradeRule countRule = new DegradeRule("getStock");
countRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
countRule.setCount(10);
countRule.setStatIntervalMs(60000);
countRule.setTimeWindow(10);
DegradeRuleManager.loadRules(Arrays.asList(rtRule, ratioRule, countRule));
5.2 熔断状态机:OPEN、HALF_OPEN、CLOSED
Sentinel的熔断状态只有三种:CLOSED(关闭)、OPEN(开启)、HALF_OPEN(半开)。
正常情况下熔断器是CLOSED,所有请求正常放行。当慢调用或异常指标达到阈值,熔断器切换到OPEN,此时所有请求都不会真正调用下游,而是直接走降级逻辑。
OPEN状态会持续timeWindow秒(就是我上面代码里的10秒)。时间到了之后,熔断器进入HALF_OPEN半开状态,会放行少量探测请求。如果这些探测请求成功了,说明下游已经恢复,熔断器重置回CLOSED;如果探测请求仍然失败,熔断器立刻回到OPEN,重新计时。
为什么一定要有HALF_OPEN?因为从OPEN直接切回CLOSED风险太大。下游可能刚刚恢复,连接池还是冷的,一下放全部流量进去又会打垮。半开状态就是给恢复过程留了一个小口子。
实际配置熔断规则时,timeWindow不宜太小也不宜太大。我见过有人设置2秒,结果熔断器频繁打开关闭,请求成功率反而更差;也有人设置60秒,下游其实5秒就恢复了,白白拒绝了几十秒流量。一般对外部RPC依赖,10秒是一个不错的起始值。
5.3 @SentinelResource实现降级兜底
熔断触发后,如果没有任何兜底逻辑,用户体验就是接口直接报错。所以要用@SentinelResource配置blockHandler和fallback:
java复制@SentinelResource(
value = "getStock",
blockHandler = "getStockBlock",
fallback = "getStockFallback"
)
public StockVO getStock(String skuId) {
return stockService.queryStock(skuId);
}
public StockVO getStockBlock(String skuId, BlockException ex) {
return StockVO.defaultStock(); // 被限流或熔断时的兜底
}
public StockVO getStockFallback(String skuId, Throwable t) {
return StockVO.defaultStock(); // 业务异常时的兜底
}
这里有个关键区别:blockHandler处理的是BlockException,也就是Sentinel拦截后的异常,包括限流、熔断、系统保护触发的拒绝;fallback处理的是方法执行过程中抛出的业务异常。如果一个请求既被限流又抛了业务异常,优先走blockHandler。
如果不想把兜底方法写在业务类里,可以用blockHandlerClass和fallbackClass指定独立的类,但要求兜底方法必须是public static:
java复制@SentinelResource(
value = "getStock",
blockHandlerClass = StockFallbackHandler.class,
blockHandler = "handleBlock",
fallbackClass = StockFallbackHandler.class,
fallback = "handleFallback"
)
public StockVO getStock(String skuId) {
return stockService.queryStock(skuId);
}
5.4 坑点:@SentinelResource的fallback不生效
这个坑我确实踩到过,而且排查了一阵子。最常见的原因有三个。
第一个是方法签名不对。兜底方法必须和原方法返回类型一致,参数列表必须包含原方法的所有参数,并且最后多一个异常参数。比如原方法是getStock(String skuId),兜底方法签名必须是getStockBlock(String skuId, BlockException ex),多一个、少一个都不行。
第二个是类内部调用导致代理失效。@SentinelResource是基于Spring AOP的,只有通过Spring容器代理调用才会生效。如果在同一个类里,a()方法内部直接this.b()调用被@SentinelResource标注的b()方法,实际上走的是this引用,而不是代理对象,兜底逻辑完全不会被触发。解决办法是把b()方法放到另一个Bean里,或者自己注入当前Bean的代理对象再调用。
第三个是资源名重复。两个不同的方法如果@SentinelResource的value配置成了同一个名字,统计节点会合并,规则也会互相干扰,熔断A接口的时候B接口也被熔断。资源名必须全局唯一,这个看起来很简单,但代码多了以后特别容易犯。
6. 热点参数限流与系统自适应保护
6.1 热点参数限流:同一接口内的差异化控制
常规限流是面向整个资源的,但有些场景需要针对特定的参数值做差异化控制。比如商品详情接口,平时每秒几十请求,但某款商品突然上了热搜,每秒几百请求都打到了同一个商品ID上。这时候如果对整个接口限流,其他正常商品的请求也会被误伤。
热点参数限流就是解决这个问题的。它按方法参数的不同值分别统计QPS。用代码注册规则:
java复制ParamFlowRule rule = new ParamFlowRule("getProduct");
rule.setParamIdx(0);
rule.setCount(10);
ParamFlowItem hotItem = new ParamFlowItem();
hotItem.setObject("1001");
hotItem.setClassType(Integer.class.getName());
hotItem.setCount(50);
rule.setParamFlowItemList(Collections.singletonList(hotItem));
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
这段配置的意思是:getProduct方法的第0个参数(也就是productId)默认限流10 QPS,但参数值等于1001时,可以放宽到50 QPS。
这里有个必踩的坑:paramIdx是从0开始的下标,如果你方法签名是getProduct(String categoryId, String productId),想按productId限流,paramIdx应该是1而不是0。配置错了控制台不报错,但规则永远不会命中。热点参数规则目前主要支持基本类型和String,传对象参数是不行的。
6.2 系统保护规则:从单机整体维度兜底
系统保护规则是Sentinel里比较特殊的一类,它并不针对某个资源,而是针对整个应用实例。主要指标包括:
| 指标 | 含义 |
|---|---|
| Highest System Load | 系统负载,仅在Linux/Unix下生效 |
| Avg RT | 所有入口流量的平均RT |
| Max Thread | 入口流量的并发线程数 |
| Entry QPS | 入口流量的总QPS |
| Highest CPU Usage | CPU使用率 |
当系统整体负荷超过这些阈值,Sentinel会限制所有入口流量。它在生产环境适合做兜底,不适合做主要限流手段。原因很简单:系统Load的Overhead高,而且规则粒度太粗,一旦触发就是一刀切。
容器环境尤其要小心。highestSystemLoad读取的是宿主机负载,如果一台宿主机上跑了很多容器,某个容器触发系统保护时,判断依据其实是整台物理机的负载,误差很大。有条件的话优先用CPU使用率,或者干脆不用这条规则。
6.3 与网关限流的配合
很多团队在网关层也会做一层限流,比如Spring Cloud Gateway整合Sentinel,或者用Gateway自带的RequestRateLimiter配合Redis令牌桶。网关限流和服务内限流的职责不同:网关管的是外部流量进入整个微服务集群的总入口,服务内限流管的是具体服务实例的资源消耗。
我的建议是两层都做。网关层可以更激进一点,流量超过集群总处理能力就直接拒绝;服务内限流侧重保护下游依赖和数据库,特别是调用链上最脆弱的环节。如果没有网关层限流,所有流量直接打到服务里,服务内的限流就成了最后一道防线,虽然能保护自己不被打垮,但上游服务已经白白消耗了网络和序列化资源。
7. 规则持久化:避免重启即失忆的经典坑
7.1 默认规则为什么不持久化
默认情况下,不管你是通过控制台添加规则,还是用FlowRuleManager.loadRules()在代码里注册规则,这些规则都只存在于当前运行进程的内存里。控制台重启、客户端重启,规则全部丢失。
开发环境这么搞问题不大,重新配一遍也就几分钟。但生产环境里规则是经过反复压测和调优的结果,重启一次就丢了,代价非常高。更麻烦的是,生产环境经常有多实例部署,你总不能一台台去控制台点一遍。
所以规则持久化是Sentinel落地生产必须解决的问题。
7.2 拉模式与推模式的对比
Sentinel官方数据源扩展支持两种模式。
拉模式是客户端定时从数据源拉取规则,比如从文件、数据库、或者Nacos中读取。优点是实现简单,不依赖额外组件;缺点是规则更新有延迟,而且客户端拉取频率不好控制,太高了浪费资源,太低了规则生效慢。
推模式是配置中心把规则变更推送给客户端。最标准的做法是:规则先写入配置中心(Nacos/Apollo),配置中心通知Sentinel控制台,控制台再推送给客户端。生产环境推荐推模式,规则变更秒级生效。
不过说实话,直接用Spring Cloud Alibaba的Nacos数据源,客户端会自动监听Nacos配置变化,虽然不是官方文档里标准的"控制台推送"架构,但实际效果已经很接近推模式了。对绝大多数团队来说,够用。真正的推送改造需要自己二次开发Dashboard,适合对规则管理要求极高的场景。
7.3 基于Nacos的推送配置示例
以Nacos为例,客户端配置如下:
yaml复制spring:
cloud:
sentinel:
datasource:
flow:
nacos:
server-addr: ${NACOS_ADDR:127.0.0.1:8848}
dataId: ${spring.application.name}-flow-rules
groupId: SENTINEL_GROUP
rule-type: flow
degrade:
nacos:
server-addr: ${NACOS_ADDR:127.0.0.1:8848}
dataId: ${spring.application.name}-degrade-rules
groupId: SENTINEL_GROUP
rule-type: degrade
然后在Nacos里创建对应的配置,dataId是order-service-flow-rules,groupId是SENTINEL_GROUP,配置格式是JSON数组:
json复制[
{
"resource": "createOrder",
"limitApp": "default",
"grade": 1,
"count": 100,
"strategy": 0,
"controlBehavior": 1,
"warmUpPeriodSec": 30
}
]
字段对应关系:grade里1是QPS,0是并发线程数;controlBehavior里0是快速失败,1是Warm Up,2是排队等待。配置这个的时候一定要查清楚字段含义,我见过有人把count写成小数,客户端日志一直在报类型转换错误。
需要特别说明:配置了Nacos数据源之后,控制台上改规则是不会自动同步回Nacos的,因为官方Dashboard默认没有实现Nacos写入。所以生产实践通常是:规则以Nacos为准,想改规则就去改Nacos配置,客户端几秒内自动感知。如果必须在控制台上改,就需要引入改造版的Dashboard,这个坑提前跟大家说清楚。
8. 踩坑实录:控制台连不上、Sentinel key not found(h0007)等
8.1 客户端连不上控制台的排查链路
我见过太多人卡在"控制台看不到服务"这一步,这里给一个完整的排查顺序。
第一步,确认客户端有没有实际请求流量。如果eager: false且没有访问任何接口,控制台看不到服务是正常的。
第二步,看客户端日志。出现sentinel register to dashboard success就说明注册成功。没出现的话,检查spring.cloud.sentinel.transport.dashboard的地址和端口写没写对。
第三步,检查8719心跳端口。客户端启动后,会占用一个本地端口用于和控制台通信。看应用启动日志里的端口号,如果被占用会自动顺延,但顺延后的端口在防火墙里可能没放行,控制台就感知不到。
第四步,确认Dashboard版本和客户端版本差距不要过大。版本跨度太大偶尔会出现心跳协议不兼容,客户端注册成功但控制台不展示的情况,两边版本尽量一致。
8.2 Sentinel key not found(h0007)的常见根因
网上关于"Sentinel key not found(h0007)"的吐槽不少,我遇到过几次,统一特征是:在控制台配置规则或加载数据源时,平台提示找不到对应的key。
这类报错按场景分,排查方向不同。如果你配置了Nacos数据源,最常见的根因是dataId在Nacos里根本不存在,或者groupId、命名空间对不上。客户端启动时尝试加载规则,找不到配置就抛这个错。处理方式是在Nacos上创建对应的配置文件,确认rule-type和文件内容格式正确。
如果你是在控制台操作时出现h0007,优先检查请求有没有被网关或代理拦截。Sentinel控制台本质是一套API,如果部署在Nginx后面,Session或请求路径被改写,控制台在解析key时找不到对应数据,也会出现这类错误。
还有一个隐蔽场景是资源名不一致。控制台配置规则时填的资源名,和代码里@SentinelResource的value或者URL路径对不上,规则下发到客户端后找不到对应的key,同样会报错。排查时可以打开客户端日志,看看Sentinel到底记录了哪些资源名,再和控制台比对。
8.3 OpenFeign整合Sentinel时熔断不生效
很多人以为引入Sentinel后,Feign调用自动就有熔断保护了,其实不然。必须在配置里显式打开开关:
yaml复制feign:
sentinel:
enabled: true
如果你用的是较新的Spring Cloud版本,配置项可能变成了:
yaml复制spring:
cloud:
openfeign:
sentinel:
enabled: true
实测下来两种配置在不同版本里都出现过,建议都试一遍然后看启动日志有没有报"SentinelFeign"相关输出。
开启之后还没完,还要在FeignClient上指定fallback类:
java复制@FeignClient(name = "stock-service", fallback = StockFeignFallback.class)
public interface StockFeignClient {
@GetMapping("/stock/{skuId}")
StockVO getStock(@PathVariable("skuId") String skuId);
}
fallback类要实现这个Feign接口,并且注册成Spring Bean。如果你需要拿到具体的异常原因,用fallbackFactory而不是fallback,这样可以在降级逻辑里记录到底是连接超时还是服务端返回错误。
8.4 压测时的误判:别让阈值设垮你的业务
最后说说压测中很容易误判的一个点。用压测工具比如JMeter打接口时,QPS是瞬间拉起来的,可能导致短时间内大量请求触发限流,把正常业务请求也一起拦了。这时候不要急着调高阈值,先看Sentinel控制台里的实时监控曲线,区分"平均QPS超了"和"瞬时脉冲超了"。
如果业务场景本身就是秒杀、抢购这种脉冲流量,建议用排队等待模式,把脉冲削平;如果是长时间平稳流量,用Warm Up给系统一个预热阶段;如果业务对延迟极其敏感,快速失败是唯一选择。
阈值设置上我的经验是:压测峰值的70%作为初始阈值,观察一段时间再微调。一次性设满是有风险的,万一系统容量预估错误,限流还没发挥作用,系统先被压垮了。
最后再分享一个实用小技巧:即使配置了eager: true,在某些内部项目里还是会偶尔遇到控制台迟迟看不到服务的情况。这时候不用干等,在启动类里加一个ApplicationRunner,启动完成后主动调用一个最简单的健康检查接口,强制触发Sentinel的埋点注册,问题基本就解决了。
java复制@Component
public class SentinelEagerInitializer implements ApplicationRunner {
private final RestTemplate restTemplate = new RestTemplate();
@Override
public void run(ApplicationArguments args) {
try {
restTemplate.getForObject("http://localhost:" + port + "/actuator/health", String.class);
} catch (Exception ignored) {
// 健康检查失败不会影响启动
}
}
}
Sentinel这套东西,上手门槛其实不高,真正需要花时间的是理解资源、规则、Slot执行链之间的联动关系,以及在不同业务场景下选对规则类型。先把流控和熔断这两块跑通,再逐步加上热点参数、系统保护、规则持久化,整个微服务的高可用治理就算有了骨架。
