1. 先想明白:Sentinel到底替我们挡什么
这几年只要做微服务,尤其是Spring Cloud Alibaba这套体系的,基本都绕不开Sentinel。它是阿里巴巴开源出来的一个轻量级高可用流量控制组件,主打两件事:限流和熔断降级。很多第一次接触的人容易把它当成一个"配置规则的工具",其实它背后解决的是分布式系统里最经典的两个事故场景。
先说第一个场景:秒杀或者热点活动。你的服务平时每秒撑个几百请求没问题,结果活动一开流量直接翻几十倍,数据库连接池先被打满,然后接口超时,超时又占用更多线程,最后整台机器卡死。这种情况靠加机器能缓解,但加机器要时间,而且流量过大时上游负载均衡本身也可能被拖垮。Sentinel做的就是在流量还没打到核心资源之前,先把多余的请求挡掉,让系统始终运行在它能够承受的范围内。注意,限流不是让系统变慢,而是主动放弃一部分请求,保住整体可用性。
第二个场景:依赖下游出问题。微服务里A调用B,B调用C,如果C突然变慢或者报错,B的线程会被大量占用,然后A调用B也超时,整个调用链跟着雪崩。Sentinel的熔断降级就是干这个的:它监控下游调用的耗时和异常比例,一旦超过阈值就快速失败,不再继续傻傻地等待下游,同时给你机会返回降级结果,比如缓存兜底或者提示文案,而不是让用户看到超时错误。
如果你之前用过Hystrix,会发现Sentinel在功能覆盖上更全面,配置也更灵活。Hystrix默认基于线程池隔离,每个依赖一个线程池,代价是线程开销大;Sentinel默认基于并发线程数和QPS做实时统计,性能开销低得多,还能做到细粒度的热点参数限流、系统自适应保护。做Java后端、Spring Cloud微服务体系,尤其是已经上了Nacos、Spring Cloud Alibaba的话,Sentinel几乎是必装的一环。下面我按一个入门者的视角,把从落地到跑通再到避坑的完整路径捋一遍。
2. 五分钟跑起来:控制台加客户端是最短的落地路径
2.1 控制台启动:一条Java命令的事
Sentinel最直观的使用方式是配合控制台(Dashboard)来做可视化规则配置和监控。控制台本质上是一个Spring Boot应用,官方直接提供了可执行Jar包。去GitHub的Sentinel Release页面下载sentinel-dashboard-xxx.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。这里有个小细节:-Dcsp.sentinel.dashboard.server这个参数本身是指定控制台地址的,启动控制台时也填自己,是为了让控制台自己也能被客户端发现。实际生产部署时,控制台地址写的是你的服务器IP,端口按需调整。
2.2 客户端接入:配置两行就够
如果你的项目用的是Spring Cloud Alibaba,接入成本低得惊人。在pom.xml里加上依赖:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>然后在application.yml里做最短配置:
spring: application: name: order-service cloud: sentinel: transport: dashboard: localhost:8080 port: 8719transport.dashboard就是控制台地址,port是客户端暴露给控制台拉取数据的端口,默认8719,如果被占用会自动向后探测找可用端口。这时候启动你的Spring Boot项目,正常情况下控制台首页的机器列表里会出现这台应用。对,就这么点配置,Sentinel的启动依赖和自动配置全部帮你处理完了。
2.3 一个必须留意的懒加载细节
很多新手在这一步就卡住了:应用启动后,控制台机器列表里啥也没有,怀疑自己配置错了。其实大概率没配错,只是因为Sentinel的默认行为是懒加载——也就是只有第一次请求访问某个接口时,这个资源才会被注册,应用才会出现在控制台里。所以接完之后别干等着,随便请求几个接口,再刷新控制台就能看到。
如果你希望应用一启动就主动向控制台注册,可以加一行配置:
spring: cloud: sentinel: eager: true这行配置建议在联调和生产环境都开着。否则你部署了一个新节点,半天控制台里看不到,排查起来容易自我怀疑。
3. 资源和规则:Sentinel整套设计的两个支点
3.1 资源到底是什么
Sentinel里面有个核心概念叫资源(Resource),它可以是任意一段代码、一个接口、一个方法,甚至是一个字符串标识。你用@SentinelResource注解标记的方法是一个资源,你通过SphU代码埋点包裹的调用链是一个资源,Spring Cloud Alibaba还会自动把你所有的Controller接口都注册成资源,资源名默认就是接口的URL。资源相当于你想保护的对象,规则则是作用在资源上的策略。
资源注册好之后,Sentinel会对它的实时调用情况做统计,比如每秒通过多少请求、最大RT、异常数。这些统计数据的精度非常高,因为它用的是滑动窗口来做时间桶计数,而不是简单的AtomicLong累加。你可以把它理解成把一分钟切成很多个小时间片,每个时间片独立计数,窗口每秒滑动一次,既能兼顾高并发写入性能,又能准确反映实时流量。
3.2 规则是打在资源上的开关
规则分很多种:流控规则、熔断降级规则、热点参数规则、系统保护规则、网关流控规则、授权规则。每种规则的生效逻辑不同,但设计思路一致:针对某个资源设置条件,条件满足就触发保护动作。
我在带团队的时候发现,很多新人一上来就急着配规则,连资源链路都没搞清楚。其实Sentinel是先有资源,再谈规则的。资源没注册,规则配得再漂亮也是白搭。
3.3 规则生效链路:请求进来怎么被拦
一个请求进来后的大致路径是这样的:先到达Sentinel的入口,找到对应的资源;接着读取该资源的规则列表,逐条判断当前请求是否命中规则;然后根据命中的规则类型执行对应动作——比如直接抛BlockException、进入排队等待、触发熔断降级逻辑。
这里要特别区分两个异常:BlockException和业务异常。被Sentinel拦截时抛出的是BlockException,它表示的是"被限流/被熔断",不是代码本身报错;而业务异常是RuntimeException之类的。两者在@SentinelResource注解里处理方式完全不同,后面我详细说。
4. 流控规则:最常用的流量闸门
4.1 阈值类型怎么选:QPS还是并发线程数
流控规则是Sentinel里干活最多的规则,也是新手误解最多的一块。
阈值类型有两种:QPS和并发线程数。QPS指每秒请求数,适合对接口的访问频率做控制,比如一个查询接口最多每秒放行50个请求。并发线程数指当前同时占用该资源的线程数量,适合处理那些单个请求耗时较长的接口。举个例子:你的接口平均耗时200ms,一台机器单线程最多每秒处理5个请求,如果QPS太高,线程就会排队。此时你与其限制QPS,不如限制并发线程数,让超出的请求快速失败,避免线程堆积。
实操经验:大部分对外接口优先按QPS限流,因为QPS直观、容易配合压测数据做估算。耗时波动大的接口,比如调用外部第三方服务,建议考虑并发线程数,否则可能出现"QPS不高但线程全被占用"的诡异现象。
4.2 流控模式:直接、关联和链路
阈值类型决定你有没有超标的"量",流控模式决定"谁触发限流时,管到谁头上"。
直接模式最直白:A接口的QPS超过阈值,就限流A接口自己。
关联模式有意思:假如B接口的QPS超过阈值,反过来对A接口限流。这非常适合读写场景。比如订单查询接口(A)和订单写入接口(B),写操作压力大时数据库资源紧张,你希望优先保障查询接口,那就对查询接口配置关联流控,关联资源指向写接口。当写接口QPS超标,查询接口自动启动限流,保护数据库不被写爆。
链路模式稍微复杂,它按调用来源做限流。最常见的例子是同一个资源被多个上游调用:用户端调用和内部定时任务都触达同一个Service方法。你希望限制用户端入口的流量,但不想限制定时任务。此时用链路模式,入口资源设为Controller层的两个不同方法,目标资源设为Service方法,针对用户端入口那条链路单独限流。
链路模式在配置时需要额外开启spring.cloud.sentinel.web-context-unify: false,否则默认会把所有链路入口合并,效果就变成全局限流了。这是老手都可能踩的坑,新手建议先用直接模式跑通。
4.3 流控效果:快速失败、预热和排队等待
限流条件命中之后,接下来是处理策略。
快速失败:超出阈值的请求直接抛BlockException。这是默认策略,也最常用。适合大部分同步接口,因为拒绝请求的成本最低。
Warm Up预热:阈值不是一步到位的,而是从初始值逐步增长到设定值,过程默认是10秒。这个效果特别适合那些冷启动时需要时间升温的系统,比如数据库连接池刚建立、JVM缓存还没热起来,一上来就放满量,反而容易把系统打垮。我个人强烈建议对刚重启过的核心服务配上Warm Up,给系统留出热身时间。
排队等待:这是最优雅但最少人用的模式。它不拒绝请求,而是让请求以固定速率通过,超出部分排队等待。默认排队超时时间为500ms,超过就不等了。在秒杀这类有大量瞬时流量、且可接受的削峰填谷场景里,用排队等待可以防止流量瞬间压垮下游。我做过一个报表导出接口,峰值QPS上千但下游数据库只能抗200,用排队等待把速率设为200 QPS之后,用户体验从"请求失败"变成"最多等几百毫秒",效果立竿见影。
流控规则的参数配置如下:
| 配置项 | 含义 | 推荐场景 |
|---|---|---|
| 阈值类型 | QPS / 并发线程数 | 对外接口用QPS,耗时波动大用并发线程 |
| 流控模式 | 直接 / 关联 / 链路 | 默认直接,读写场景用关联,多调用方用链路 |
| 流控效果 | 快速失败 / Warm Up / 排队等待 | 同步接口用快速失败,系统冷启动用Warm Up,削峰填谷用排队 |
阈值怎么定?我建议不要拍脑袋。先跑压测,找到系统性能拐点,然后留出20%-30%的余量。比如压测发现某接口在QPS 300时RT开始明显上涨,那阈值就定200-250,再配合Warm Up让系统平滑到达极限。
5. 熔断降级规则:依赖崩了以后怎么做
5.1 三种熔断策略详解
熔断降级是Sentinel保护系统周全身的另一个轮子。它的思路是:不阻止请求进入,而是当你发现这个资源已经出故障时,直接跳过它或快速失败。
Sentinel 1.8之后提供三种熔断策略:
慢调用比例。设置一个最大RT,比如500ms。滑动窗口内请求数达到最小请求数(默认5),且慢调用比例超过阈值(比如0.6),就触发熔断。这个策略对"接口变慢但不报错"的场景非常有用。我曾经处理过一个下游支付状态查询接口,平时RT平均80ms,调用方代码有问题后突然变成2s,但没有抛异常。用慢调用比例熔断,最大RT设500ms,比例阈值0.5,它就能精准识别这种"钝刀子割肉"式的恶化。
异常比例。窗口内请求数达到最小请求数后,异常数占总请求数的比例超过阈值就熔断。适合接口开始大量报错的场景。比如一个接口突然因为某个数据源变化导致大量空指针,异常比例飙升到80%,熔断就可以立即启动。
异常数。窗口内异常数量超过阈值直接熔断。注意,这里的最小请求数不会作用于异常数策略,也就是说即使请求量很低,只要异常数攒够也会熔断。适合高可用要求极高的核心链路,宁可错杀不可放过。
5.2 熔断之后发生了什么
触发熔断后,资源会进入打开(Open)状态,此时所有请求都会被拦截,快速失败。经过设定的熔断时长(默认1000ms)后,进入半开(Half-Open)状态,Sentinel会放行少量请求探测资源是否恢复。如果探测请求都成功了,熔断关闭,资源恢复正常;如果还是失败,重新打开熔断。
这里有个细节容易被忽略:熔断状态是针对"资源"而不是针对"某个实例"或"某次调用"的。所以在分布式架构下,每个实例的熔断状态是独立的,A实例熔断了不代表B实例也熔断。判断是否恢复,靠的是每个实例自己的半开探测,这也就意味着,如果下游故障持续,你可能会看到各实例熔断时段不一致,这是正常现象。
5.3 一套比较稳妥的降级参数经验值
熔断降级最大的坑就是阈值定得太激进。我有一次把异常比例熔断阈值设成0.1,结果一次全链路压测里一个非核心接口抖动,触发熔断后大量请求走了降级方法,把降级方法依赖的缓存又打满了,反而扩大了故障面。
给一个相对稳妥的参数参考:
| 策略 | 参数 | 参考值 |
|---|---|---|
| 慢调用比例 | 最大RT | 接口压测P99 RT的2-3倍 |
| 慢调用比例 | 比例阈值 | 0.4-0.6 |
| 慢调用比例 | 熔断时长 | 5000-10000ms |
| 异常比例 | 比例阈值 | 0.2-0.5 |
| 异常比例 | 最小请求数 | 10-20 |
| 异常数 | 阈值 | 20-50 |
核心思路是:熔断阈值要比你自己的人工报警阈值宽松,因为它主要负责兜底,而不是第一时间报警。发现的问题交给监控告警,熔断负责的是阻止问题继续蔓延。
6. 注解接入与异步问题:@SentinelResource 的正确打开方式
6.1 blockHandler 和 fallback,以及两者别搞混
Spring Cloud Alibaba的自动配置虽然把Controller接口都注册成了资源,但更灵活、更精细的控制还是要靠@SentinelResource注解来做代码埋点。这个注解有两个核心属性:blockHandler和fallback。
fallback负责处理业务异常,也就是方法本身抛出的RuntimeException。
blockHandler负责处理Sentinel拦截产生的BlockException,也就是被限流/熔断时触发的逻辑。
我见过很多新手的坑在于:只配了fallback,以为限流之后会自动走降级逻辑,结果发现被限流的请求直接抛了BlockException,前端报错。原因是两者互不兜底。想要既处理业务异常又处理限流,必须同时配置:
@SentinelResource( value = "order.create", blockHandler = "createOrderBlockHandler", fallback = "createOrderFallback" ) public Order createOrder(Long userId, Long skuId) { // 业务逻辑 return orderService.create(userId, skuId); } // 注意:blockHandler 和 fallback 的方法签名,入参要和原方法一致,最后可以加一个 BlockException / Throwable 参数 public Order createOrderBlockHandler(Long userId, Long skuId, BlockException ex) { return new Order(); } public Order createOrderFallback(Long userId, Long skuId, Throwable throwable) { return new Order(); }还有一点:blockHandler处理的优先级高于fallback。如果同时触发业务异常和限流,只会走blockHandler。
6.2 异步场景下注解失效问题
如果你在CompletableFuture.supplyAsync(() -> doSomething())这类异步会签里调用被@SentinelResource注解的方法,你会发现限流规则完全不生效。这不是你配置错了,而是Sentinel原生的注解适配器默认基于ThreadLocal传递上下文,异步线程拿不到主线程的调用链入口信息,于是资源统计就断掉了。
解决方式有两种:第一,别在异步线程里直接调用注解方法,而是在主线程先获取SphU.entry或者AsyncEntry,把资源入口上下传到异步线程里去。第二,如果你的团队里异步场景特别多,建议好好研究一下Sentinel的entry和asyncEntry两个API。asyncEntry是专门为异步场景设计的,它把资源统计和调用链解耦,统计的是真实的资源调用情况,而不是依赖当前线程的上下文。
我自己的经验是:注解方式适合90%的同步场景,价格最便宜;但一旦代码里出现线程池、MQ消费、GateWay配异步过滤器,就要主动排查一下资源埋点是不是还在生效。系统上线前,我一定会拿两三个核心异步链路做一次"限流触发"验证,确认确实拦得住再收工。
6.3 热点参数限流的小补充
@SentinelResource还有一个特别实用的场景:热点参数限流。比如同一个商品详情接口,普通商品的QPS阈值是100,但某个爆款商品的QPS到了1000,你想单独对爆款商品的请求参数做限制。用热点参数规则可以做到:针对资源goods.detail的第一个参数(商品ID),单独设置更高或者更低的阈值和参数例外项。
热点参数限流需要额外引入sentinel-parameter-flow-control依赖,并且这个规则是在专业控制台的"热点参数规则"里配置,不是在普通流控规则里。它非常适用于秒杀场景下的"单商品限流""单用户限流",很多系统能做出来的精准限流都靠它。
7. 网关接入:Spring Cloud Gateway 用 Sentinel 限流的实践
7.1 为什么网关也要限流
如果是单机应用,只在服务端做流控就够了。但在微服务架构里,所有外部流量都要经过网关,网关是第一道防线。在网关层限流的好处是:过滤掉的请求根本不会到达后端服务,节省的是整条链路的资源,而不只是某一个被限流接口的线程。
Spring Cloud Gateway接入Sentinel也很顺,依赖换一下:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency>这里要注意:网关的自动配置和普通Spring MVC应用的自动配置是互斥的。如果你在网关项目里直接引spring-cloud-starter-alibaba-sentinel,它不会自动对路由生效,因为网关用的不是Servlet模型,而是WebFlux模型。
7.2 网关维度配置
网关接入后,你会看到控制台多出两个维度:一个叫Route ID维度(按路由ID限流),一个叫自定义API分组维度(按你自定义的API组合限流)。
Route ID维度最简单:在控制台的"网关流控规则"里选择一条路由,直接设置QPS阈值就行。比如/order/**这个Route的QPS阈值设为500,超过的请求直接返回默认的429 Too Many Requests。
API分组维度的能力更强。比如你希望/order/**和/user/**共享同一个总阈值,或者更常见的是,你希望把一组内部测试接口统一限流掉,可以用API分组把它们归到同一个分组,然后对这个分组配置流控。这块我觉得是网关层限流最精髓的地方:它不是按URL机械限流,而是按业务语义组合限流。
7.3 网关流控与业务流控的区别
网关限流主要针对入口流量,粒度通常较粗,一般按路由或API分组来限制,好处是统一入口、统一管控。业务流控主要在服务内部,粒度更细,可以精确到方法和参数,比如热点参数规则、关联流控都只能在服务内生效。
两层限流没有谁替代谁的关系。最佳实践是两层都上:网关层挡住突发洪峰,服务层保护性能瓶颈所在的资源。比如秒杀场景,网关限流把整体流量降到1w QPS,订单服务内部再把订单创建接口的QPS限到3000,数据库层的连接池才安全。这就是典型的层层设防。
8. 规则不生效与控制台不显示的排错名场面
8.1 控制台没出现应用
这个现象在接入网关或WebFlux项目时尤其多,但我按普通Spring Boot项目讲。按照如下顺序排查:
- 看
spring.cloud.sentinel.transport.dashboard配置是否有效,确认路径走的是/favicon.ico或某个不存在的路径,第一次访问触发了注册。 - 检查应用启动日志,是否有
Sentinel started相关日志。如果看到Sentinel reporter初始化失败,多半是8719端口被占用。 - 打开
http://localhost:8719/registry?transport.dashboard=localhost:8080,这个地址会返回注册信息。如果注册成功,客户端会返回一个JSON格式数据。
如果以上都没问题,注意一点:新版控制台和应用之间的通信是走HTTP的,如果你的应用在容器里部署,需要确保transport.port(8719)端口能被控制台所在的机器访问。
8.2 规则设置成功却不拦截
这是最打击人的一个情况:控制台规则配好了,资源名也显示在线,压测时却发现规则根本没生效。我踩过的坑主要是:
- 版本不匹配。服务端Sentinel Core版本和控制台版本不一致,会导致控制台推送的规则序列化失败。官方推荐版本尽量保持一致,跨大版本(比如1.8和2.0)一定会有兼容问题。
- 规则没真正下发到客户端。控制台配的规则默认只保存在控制台内存里,如果应用重启或者控制台重启,规则就丢了。那自然会出现"规则配了但过一段又失效"的情况。这个问题的根治方案是规则持久化,下一小节说。
- 资源配置写错。在注解为资源但URL本身也被自动注册时,容易出现你配置的是URL资源,但代码里触发的是注解资源,两边名字差一点规则就空了。
8.3 规则重启就丢,持久化方案
这是所有用Sentinel的人都绕不开的痛点:默认情况下,你在控制台里配的规则只存在控制台进程中,应用重启、控制台重启,规则全部归零。
生产环境绝对不能用这种方式管理规则。常见做法有以下几种:
方案一:使用Nacos做规则数据源(推模式)。在客户端引入数据源扩展:
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>配置文件里声明:
spring: cloud: sentinel: datasource: flow: nacos: server-addr: localhost:8848 dataId: ${spring.application.name}-flow-rules groupId: SENTINEL_GROUP rule-type: flow这样客户端会启动时从Nacos拉取规则,并且监听配置变更,规则一变立即生效。这是目前最推荐的持久化方案,因为规则可以统一在Nacos管理,变更方便。
方案二:本地文件持久化(拉模式)。把规则写到本地文件里,客户端启动时读取,定期检查文件变更。适合不引入配置中心的场景。缺点是多个实例之间规则同步麻烦,改一个文件要逐步发布。
我自己在实际项目中的体会是:如果团队还没有配置中心,那就一次性把Nacos上马,因为Sentinel规则这种需要全局管理的东西,指望本地文件做多实例同步迟早出事故。用Nacos管理之后,每次规则调整都有变更记录,回滚也方便,新接手的同事查规则看Nacos配一下就懂了。
最后再分享一个小经验:刚开始用Sentinel别贪多,先挑两个核心链路配上流控和熔断,跑一两周看看统计面板上的指标,再慢慢拓展规则。我见过不少人第一天就把所有接口的流控规则全配了,结果规则互相打架,排查了一整天。Sentinel这东西,先跑通你读得懂配置的场景,比什么都重要。等你真正遇到一次流量洪峰,看着控制台上的拦截曲线稳稳把系统扛住,就会理解前面这些配置的价值了。