Spring Cloud Alibaba Sentinel使用教程
什么是Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。
Sentinel以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
分布式系统的流量防卫兵,保护你的微服务
Sentinel的核心概念
流量监控
每个微服务能承载的流量是有限的,Sentinel能控制访问微服务的流量,当流量过大时,会进行限流,保证服务不会被压垮
熔断降级
微服务中会出现因网络等出现某个链路节点不可用的状态,在这个服务请求链路就会在这里卡住,当大量的连接堆积,就会占用服务器的资源,达到一定程度,服务就会崩溃
熔断降级就是,通过对链路进行监控,当某个链路节点出现故障时,让访问这个节点服务进行快速失败或稍后重试等机制,从而保护系统资源不会因某个链路节点的故障导致耗尽
某个节点的故障会导致整个链路的故障,这个链路的节点又被多个服务使用,所以一个链路节点的故障可能会导致整个系统的崩溃,熔断降级就是解决这个问题的机制
系统负载保护
根据系统能够处理的请求,和允许进来的请求,来做平衡,追求的目标是在系统不被拖垮的情况下,提高系统的吞吐率
消息削峰填谷
某瞬时来了大流量的请求,而如果此时要处理所有请求,很可能会导致系统负载过高,影响稳定性。但其实可能后面几秒之内都没有消息投递,若直接把多余的消息丢掉则没有充分利用系统处理消息的能力
Sentinel的RateLimiter模式能在某一段时间间隔内以匀速方式处理这样的请求,充分利用系统的处理能力,也就是削峰填谷,保证资源的稳定性
Sentinel两大组成部分
- 核心库
- (Java客户端)不依赖任何框架/库,能够运行在所有Java运行时环境,对SpringCloud有较好的支持
- 仪表盘
- (Dashboard)基于SpringBoot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器
搭建Sentinel仪表盘服务
搭建Sentinel仪表盘监控各个微服务的使用情况
-
下载Sentinel Dashboard
-
运行Sentinel Dashboard
- 切换到对应的目录,使用
java -jar sentinel-dashboard-1.8.8.jar
命令运行 - 默认使用的端口是8080,可以通过参数server.port指定运行的端口
java -jar sentinel-dashboard-1.8.8.jar --server.port=8888
- 切换到对应的目录,使用
-
通过浏览器访问Sentinel Dashboard
- 访问http://localhost:8080
- 登陆的默认账号是sentinel,默认密码是sentinel
将Sentinel客户端集成到微服务模块中
-
在微服务模块中加入Sentinel依赖
<!--引入Sentinel客户端依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
-
在微服务模块中配置Sentinel客户端
- 通过"spring.cloud.sentinel.transport.dashboard"参数配置Sentinel仪表盘服务端的地址
- 通过"spring.cloud.sentinel.transport.port"参数配置Sentinel客户端通过哪个端口与Sentinel仪表盘进行通信,默认是8197,如果被占用会自动自增+1,直到找到可用的端口
spring: cloud: sentinel: transport: #配置Sentinel仪表盘服务端的地址 dashboard: localhost:8080 #配置Sentinel客户端通过哪个端口与Sentinel仪表盘进行通信,默认是8197,如果被占用会自动+1,直到找到可用的端口 port: 8197
-
运行微服务,然后通过postman进行接口调用,最后查看Sentinel Dashboard验证微服务成功集成Sentinel客户端
- QPS:Queries Per Second(每秒查询率),是服务器每秒响应的查询次数
Sentinel的流量监控
- Sentinel中的流量监控规则
- 资源名∶唯一名称,默认请求路径
- 针对来源∶Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
- 阈值类型
- QPS(每秒钟的请求数量)∶当调用该api的QPS达到阈值的时候,进行限流
- 线程数∶当调用该api的线程数达到阈值的时候,进行限流
- 流控模式
- 直接∶api达到限流条件时,直接限流
- 关联∶当关联的资源达到阈值时,就限流自己,也就是限流当值配置规则的资源
- 链路∶当从某个接口过来的资源达到限流条件时,开启限流
- 流控效果
- 快速失败∶直接失败,抛异常
- WarmUp∶根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
- 排队等待∶匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效
基于QPS的直接流控案例
案例:对一个资源进行QPS为1的限流
-
在Sentinel控制台的"簇点链路"选项中选中一个资源,点击流控进行添加流控规则
-
对资源进行QPS限流,QPS大小设置为1
-
在控制台中的流控规则中可以看到新增的流控规则
-
通过postman测试流控是否成功
- 正常访问,请求成功,能够获取数据
- 异常高频访问,请求失败,接口成功被限流
-
1
基于QPS的关联流控案例
案例:当关联资源"t2"的QPS大于1时,限流资源"t1"
-
对资源"t1"添加关联流控规则,关联资源为"t2"
-
在流控规则中查看新增的规则
-
使用postman对接口"t2"进行QPS为50,间隔为200ms,同时访问接口"t1",验证限流效果
- 使用postman模拟并发访问接口"t2"
- 当"t2"处于并发时访问"t1",验证限流效果
Sentinel的熔断降级
注意,如果没有配置持久化规则,配置的熔断策略和限流规则会在Sentinel重启后丢失
线程堆积引出熔断降级
- 一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方API等
- 例如,支付的时候,可能需要远程调用银联提供的API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用
- 这时,我们对不稳定的服务进行熔断降级,让其快速返回结果,不要造成线程堆积
- 现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路
- 链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用
- 因此需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩
熔断,降级,限流三者的关系
- 熔断强调的是服务之间的调用能实现自我恢复的状态
- 限流是从系统的流量入口考虑,从进入的流量上进行限制,达到保护系统的作用
- 降级,是从系统业务的维度考虑,流量大了或者频繁异常,可以牺牲一些非核心业务,保护核心流程正常使用
- 熔断是降级方式的一种,降级又是限流的一种方式,三者都是为了通过一定的方式在流量过大或者出现异常时,保护系统的手段
熔断策略
flowchart TD
A[请求进入] --> B{熔断器状态?}
B -- OPEN --> C[快速失败, 请求被熔断]
B -- HALF-OPEN --> D[允许请求通过进行探测]
D --> E{请求结果?}
E -- 成功 --> F[关闭熔断器<br>恢复流量]
E -- 失败/慢调用 --> G[再次熔断<br>重置熔断时长计时器]
B -- CLOSED --> H[允许请求通过]
H --> I{记录请求结果<br>更新统计指标}
I --> J{触发熔断条件?}
J -- 是 --> K[开启熔断<br>设置状态为OPEN<br>启动熔断时长计时器]
J -- 否 --> L[保持CLOSED状态]
K --> M[进入熔断时长等待]
M --> N[熔断时长结束?]
N -- 是 --> O[进入半开状态<br>设置状态为HALF-OPEN]
subgraph 熔断条件判断
J
end
subgraph 熔断条件详情
J_Input[单位统计时长内请求数>minRequests?]
J_Input --> J1{策略类型?}
J1 -- 慢调用比例 --> J2[慢调用比例>阈值?]
J2 -- 是 --> J_Yes
J2 -- 否 --> J_No
J1 -- 异常比例 --> J3[异常比例>阈值?]
J3 -- 是 --> J_Yes
J3 -- 否 --> J_No
J1 -- 异常数 --> J4[异常计数>阈值?]
J4 -- 是 --> J_Yes
J4 -- 否 --> J_No
J_Yes[Judge: 是] --> J
J_No[Judge: 否] --> J
end
慢调用比例
- 慢调用比例(SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用
- 当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
- 熔断时长结束后,熔断器会进入探测恢复状态(HALF-OPEN状态),若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT则会再次被熔断
- 参考配置图
异常比列
- 异常比例(ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
- 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN状态)
- 若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断
- 参考配置图
异常数
- 异常数(ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断
- 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN状态)
- 若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断
- 参考配置图
基于慢调用比例熔断策略的案例
- 需求:
- 当"t3"接口在一秒钟内,调用"t3"接口的请求数大于等于五个,且这五个请求响应时间超过200ms的比例超过50%时,触发"t3"接口的熔断,熔断时间为十秒
-
在"簇点链路"选项中,找到对应的接口,点击熔断进行熔断规则添加
-
配置对应的熔断规则
- 熔断策略选择慢调用比例
- 最大RT(Request Time)设置为200ms
- 触发熔断的比例阈值设置50%
- 熔断时间设置为10s
- 触发熔断统计最小请求数设置为5
- 统计时长设置1000ms
-
添加成功后,可以在熔断规则中查看到新增的熔断规则
-
在"t3"接口的代码中加入睡眠300ms,模拟执行耗时的操作
package anyi.space.memberServer.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @ProjectName: distributedSystemLearn * @FileName: SentinelTestController * @Author: 杨逸 * @Data:2025/9/16 14:36 * @Description: 测试Sentinel流控规则的控制器 */ @RestController @RequestMapping("/sentinel") public class SentinelTestController { @GetMapping("/t3") public String t3(){ String msg = "t3被执行"; try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(msg); return msg; } }
-
使用postman调用"t3"接口,模拟并发,验证熔断机制是否触发
- 使用postman模拟20个用户并发调用"t3"接口,持续十分钟
- 然后在"t3"模拟并发时,测试"t3"接口,验证触发熔断成功
- 模拟并发结束后,等待十秒的熔断回复期过后,再次访问"t3"接口,验证熔断已恢复
基于异常比例的熔断策略案例
- 需求:
- 在统计时间内,请求数大于等于5,接口出现错误的比例大于等于{50\%},则触发熔断机制
-
配置熔断策略
- 熔断策略使用异常比例
- 触发阈值设置50%
- 熔断时间设置为5s
- 触发熔断统计最小请求数设置为5
- 统计时长设置1000ms
-
在"t4"接口中模拟一个异常,异常出现的比例为{50\%}
package anyi.space.memberServer.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @ProjectName: distributedSystemLearn * @FileName: SentinelTestController * @Author: 杨逸 * @Data:2025/9/16 14:36 * @Description: 测试Sentinel流控规则的控制器 */ @RestController @RequestMapping("/sentinel") public class SentinelTestController { private int number = 1; /** * t4为测试Sentinel基于异常比例熔断策略的测试接口 * @return */ @GetMapping("/t4") public String t4(){ String msg = "t4被执行"; //制造一个异常 number++; int r = number/(number%2); System.out.println(msg); return msg; } }
-
使用postman模拟并发调用"t4"接口,验证熔断策略是否生效
- 正常访问,服务每隔一次调用出现一次错误
- 模拟并发访问后,再访问接口,接口被熔断
- 等待熔断时间过后,再次访问,接口熔断被恢复
基于异常数的熔断策略案例
- 需求
- 在统计时间内,接口出现错误的次数大于等于10时,则触发熔断策略
-
配置基于异常数的熔断策略
-
在"t5"接口中制造一个异常
package anyi.space.memberServer.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @ProjectName: distributedSystemLearn * @FileName: SentinelTestController * @Author: 杨逸 * @Data:2025/9/16 14:36 * @Description: 测试Sentinel流控规则的控制器 */ @RestController @RequestMapping("/sentinel") public class SentinelTestController { /** * t5为测试Sentinel基于异常数熔断策略的测试接口 * @return */ @GetMapping("/t5") public String t5(){ String msg = "t5被执行"; //制造一个异常 int r = 5/0; System.out.println(msg); return msg; } }
-
使用postman验证熔断机制是否生效
- 开始调用时,"t5"接口访问成功,并出现错误
- 调用"t5"接口出现错误一段时间后,触发熔断策略,"t5"接口的访问被限制
Sentinel的热点规则
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。
热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效
热点规则案例
- 需求
- 对资源"news"配置热点规则,当资源出现并发,触发热点规则,修改返回信息提示信息,模拟使用了特定逻辑处理过了热点资源
- 场景
- 对资源"news"配置热点规则,当资源出现并发,触发热点规则,我们可以对热点资源进行特定的处理,比如:直接限流熔断,或者将热点资源缓存起来等等
-
在为服务中定义"news"资源接口,并指定触发热点限流的处理器
- 使用"@SentinelResource"注解标识一个资源,value为资源名称,blockHandler为流控规则触发后的处理方法
package anyi.space.memberServer.controller; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @ProjectName: distributedSystemLearn * @FileName: SentinelTestController * @Author: 杨逸 * @Data:2025/9/16 14:36 * @Description: 测试Sentinel流控规则的控制器 */ @RestController @RequestMapping("/sentinel") public class SentinelTestController { /** * news为测试Sentinel热点规则的接口 * @param id * @param type * @return */ @GetMapping("/news") //使用@SentinelResource注解标识一个资源,value为资源名称,blockHandler为流控规则触发后的处理方法 @SentinelResource(value = "news",blockHandler = "newsBlockHandler") public String news(Integer id,String type){ String msg = "id为%d类型为%s的news被访问".formatted(id, type); System.out.println(msg); return msg; } /** * newsBlockHandler为news接口流控规则触发后的处理方法 * @param id * @param type * @param e * @return */ public String newsBlockHandler(Integer id, String type, BlockException e){ String msg = "id为%d类型为%s的news被限流".formatted(id, type); System.out.println(msg); return msg; } }
-
在Sentinel Dashboard中配置"/news"接口的热点规则
- 资源名配置为"news",与微服务中"@SentinelResource"注解中的value值保持一直
- 参数索引设置为0,表示统计"/news"接口第一个参数
- 单机阈值设置为10,统计窗口设置为1,表示在一秒内该热点被访问十次,则触发热点规则
-
在热点规则选项中可以看到新增的热点规则
-
使用postman模拟并发调用"news"接口,验证是否成功触发热点规则
- 刚开始调用"/news"接口成功
- 并发后触发热点规则,返回了热点规则触发后的处理信息
-
配置热点参数,在热点规则的基础上设置热点参数,对特定参数的资源进行特殊处理
当请求的参数配置热点参数则统计进热点参数的规则,否则统计进行热点规则
- 在热点规则中找到编辑按钮
- 打开高级选项,进行热点参数配置
- 参数类型设置为"int"
- 参数值设置为1
- 阈值设置为5
- 然后点击添加
Sentinel系统规则
什么是系统规则
- 一个问题引出系统规则,如我们系统最大性能能抗100QPS,如何分配/t1/t2的QPS?
- 方案1:/t1分配QPS=50/t2分配QPS=50,问题,如果/t1当前QPS达到50,而/t2的QPS才10,会造成没有充分利用服务器性能
- 方案2:/t1分配QPS=100/t2分配QPS=100,问题,容易造成系统没有流量保护,造成请求线程堆积,形成雪崩.
- 有没有对各个资源请求的QPS弹性设置,只要总数不超过系统最大QPS的流量保护规则?==>系统规则
- 系统规则作用,在系统稳定的前提下,保持系统的吞吐量
我们把系统处理请求的过程想象为一个水管,到来的请求是往这个水管灌水,当系统处理顺畅的时候,请求不需要排队,直接从水管中穿过,这个请求的RT是最短的;反之,当请求堆积的时候,那么处理请求的时间则会变为:排队时间 + 最短处理时间
Sentinel支持的系统规则
- 配置参考图
- Load自适应(仅对Linux/Unix-like机器生效):系统的load1作为启发指标,进行自适应系统保护。当系统load1超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR阶段)。系统容量由系统的{maxQps*minRt}估算得出。设定参考值一般是{CPU core * 2.5}
- CPUusage(1.5.0+版本):当系统CPU使用率超过阈值即触发系统保护(取值范围0.0-1.0),比较灵敏
- 平均RT:当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是毫秒
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护
- 入口QPS:当单台机器上所有入口流量的QPS达到阈值即触发系统保护
基于入口QPS的系统保护规则案例
- 需求
- 当进入系统流量的QPS为2时(方便测试),触发系统保护规则,系统被阻塞保护
-
配置系统保护规则
- 选择"入口QPS"类型
- 触发阈值设置为2
-
在系统规则选项中可以查看新增的系统保护规则
-
使用postman测试系统保护规则是否生效,未测试成功
- 未测试成功,使用postman和jmeter进行压力测试均未出现被限流的响应结果
- 但Sentinel Dashboard实时监控,可以看到有被拒绝的请求
Sentinel的"@SentinelResource"详解
"@SentinelResource"注解属性
- value:资源名称,必需项(不能为空)
- entryType:entry类型,可选项(默认为EntryType.OUT)
- blockHandler/blockHandlerClass:blockHandler对应处理BlockException的函数名称,可选项。blockHandler函数访问范围需要是public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException。blockHandler函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定blockHandlerClass为对应的类的Class对象,注意对应的函数必需为static函数,否则无法解析
- fallback/fallbackClass:fallback函数名称,可选项,用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback函数签名和位置要求
- 返回值类型必须与原函数返回值类型一致
- 方法参数列表需要和原函数一致,或者可以额外多一个Throwable类型的参数用于接收对应的异常
- fallback函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定fallbackClass为对应的类的Class对象,注意对应的函数必需为static函数,否则无法解析
- defaultFallback(since1.6.0):默认的fallback函数名称,可选项,通常用于通用的fallback逻辑(即可以用于很多服务或方法)。默认fallback函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了fallback和defaultFallback,则只有fallback会生效。defaultFallback函数签名要求
- 返回值类型必须与原函数返回值类型一致
- 方法参数列表需要为空,或者可以额外多一个Throwable类型的参数用于接收对应的异常
- defaultFallback函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定fallbackClass为对应的类的Class对象,注意对应的函数必需为static函数否则无法解析
- exceptionsToIgnore(since1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入fallback逻辑中,而是会原样抛出
全局blockHandler
-
新增一个"t6"接口,并使用"@SentinelResource"注解指定阻塞处理类(blockHandlerClass),和阻塞处理方法(blockHandler)
/** * @ProjectName: distributedSystemLearn * @FileName: SentinelTestController * @Author: 杨逸 * @Data:2025/9/16 14:36 * @Description: 测试Sentinel流控规则的控制器 */ @RestController @RequestMapping("/sentinel") public class SentinelTestController { /** * 使用全局阻塞处理方法处理 * 如果没有指定blockHandlerClass,则对应的blockHandler必须为当前类的public方法 * 指定了blockHandlerClass属性,则对应的blockHandler必须为对应类的静态方法 * blockHandler指定的方法参数和当前方法保持一致,除了最后一个参数,最后一个参数必须是BlockException类型 * @return {@code String } * @description: * @author: 杨逸 * @data:2025/09/19 16:19:22 * @since 1.0.0 */ @SentinelResource(value = "t6",blockHandlerClass = MySentinelBlockHandler.class,blockHandler = "myBlockHandler") @GetMapping("/t6") public String t6(){ String msg = "t6被执行"; System.out.println(msg); return msg; } }
-
创建全局阻塞处理类,并编写对应的阻塞处理方法
package anyi.space.memberServer.components; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * @ProjectName: distributedSystemLearn * @FileName: MySentinelBlockHandler * @Author: 杨逸 * @Data:2025/9/19 15:54 * @Description: 自定义全局Sentinel的降级处理 */ public class MySentinelBlockHandler { /** * 自定义限流处理 * 必须是静态方法,返回值与@SentinelResource注解的标识方法的返回值保持一致 * 参数列表与@SentinelResource注解标识方法的参数列表保持一致,可以多一个BlockException参数 * @param e * @return */ public static String myBlockHandler(BlockException e) { e.printStackTrace(); String msg = "自定义的Sentinel全局myBlockHandler被触发"; System.out.println(msg); return msg; } }
-
在Sentinel Dashboard中配置"t6"的限流规则
- 配置QPS为2,方便测试验证
-
使用postman验证自定义全局限流处理类是否生效
- 正常调用返回正确的结果
- 并发调用后返回blackHandler处理后的结果
fallback
- blockHandler只负责sentine控制台配置违规
- fallback负责Java异常/业务异常
使用"fallback"处理出现业务异常的案例
- 需求
- 当"t7"出现业务异常时,通过"@SentinelResource"注解的"fallback"属性指定对应的处理方法
-
新增"t7"接口,并使用"@SentinelResource"注解的"fallback"属性指定对应的处理方法
package anyi.space.memberServer.controller; import anyi.space.memberServer.components.MySentinelBlockHandler; import anyi.space.memberServer.components.MySentinelFallbackHandler; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; /** * @ProjectName: distributedSystemLearn * @FileName: SentinelTestController * @Author: 杨逸 * @Data:2025/9/16 14:36 * @Description: 测试Sentinel流控规则的控制器 */ @RestController @RequestMapping("/sentinel") public class SentinelTestController { private int number = 1; /** * 业务异常处理类测试使用的接口 * 如果没有指定fallbackClass,则fallback必须为当前类的public方法 * 指定了fallbackClass,则fallback必须为对应类的public静态方法 * fallback指定的方法参数和当前方法保持一致,除了最后一个参数,最后一个参数必须是Throwable类型 * @return {@code String } * @description: * @author: 杨逸 * @data:2025/09/19 16:27:27 * @since 1.0.0 */ @SentinelResource(value = "t7",fallbackClass = MySentinelFallbackHandler.class,fallback = "myFallbackHandler",blockHandlerClass = MySentinelBlockHandler.class,blockHandler = "myBlockHandler") @GetMapping("/t7") public String t7(){ String msg = "t7被执行"; number++; if (number % 5 == 0){ //模拟业务出现异常 int e = 1/0; } System.out.println(msg); return msg; } }
-
创建一个业务异常处理类MySentinelFallbackHandler,并编写对应的fallback处理方法
package anyi.space.memberServer.components; /** * @ProjectName: distributedSystemLearn * @FileName: MySentinelFallbackHandler * @Author: 杨逸 * @Data:2025/9/19 16:21 * @Description: 自定义Sentinel的全局业务异常处理类 */ public class MySentinelFallbackHandler { /** * 自定义业务异常处理方法 * 必须是静态方法 * @param e * @return {@code String } * @description: * @author: 杨逸 * @data:2025/09/19 16:23:50 * @since 1.0.0 */ public static String myFallbackHandler(Throwable e){ String msg = "出现业务异常=" + e.getMessage(); System.out.println(msg); return msg; } }
-
使用postman测试"t7"接口,验证自定义业务处理方法是否生效
- 开始访问时,结果正常
- 多次访问后出现,异常处理的结果
Sentinel + OpenFeign + Nacos整合
服务消费方集成Openfeign
-
在服务消费方导入Openfeign依赖
<!--导入OpenFeign的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>3.1.5</version> </dependency>
-
编写服务提供方对应接口的OpenFeign接口
package anyi.space.memberConsumer.service; import anyi.sapce.common.entity.Member; import anyi.sapce.common.entity.ResponseResult; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.*; /** * @ProjectName: distributedSystemLearn * @FileName: MemberOpenFeignService * @Author: 杨逸 * @Data:2025/9/19 16:41 * @Description: 使用OpenFeign的接口 */ @Component @FeignClient("member-service-provider") public interface MemberOpenFeignService { @GetMapping("/member-provider/member") ResponseResult<Member> getById(@RequestParam("id") Long id); @PostMapping("/member-provider/member") boolean save(@RequestBody Member member); }
-
在服务消费方使用OpenFeign实现的远程调用
package anyi.space.memberConsumer.controller; import anyi.sapce.common.entity.Member; import anyi.sapce.common.entity.ResponseResult; import anyi.space.memberConsumer.service.MemberOpenFeignService; import lombok.RequiredArgsConstructor; import org.springframework.util.DigestUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.nio.charset.StandardCharsets; /** * @ProjectName: distributedSystemLearn * @FileName: MemberConsumerController * @Author: 杨逸 * @Data:2025/4/13 20:07 * @Description: */ @RequiredArgsConstructor @RestController @RequestMapping("/member") public class MemberConsumerController { //private final MemberService memberService; //使用OpenFeign的实现类 private final MemberOpenFeignService memberOpenFeignService; @GetMapping public ResponseResult<Member> getMemberById(Long id){ return memberOpenFeignService.getById(id); } @PostMapping public ResponseResult addMember(Member member){ String hex = DigestUtils.md5DigestAsHex(member.getPwd().getBytes(StandardCharsets.UTF_8)); member.setPwd(hex); boolean save = memberOpenFeignService.save(member); return ResponseResult.okResult(save); } }
-
在启动类使用"@EnableFeignClients"注解启用OpenFeign功能
package anyi.space.memberConsumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; /** * @ProjectName: distributedSystemLearn * @FileName: MemberConsumerApplication * @Author: 杨逸 * @Data:2025/4/13 20:05 * @Description: */ @EnableFeignClients @EnableDiscoveryClient @SpringBootApplication(exclude= DataSourceAutoConfiguration.class) public class MemberConsumerApplication { public static void main(String[] args) { SpringApplication.run(MemberConsumerApplication.class,args); } }
-
使用postman测试接口
- 成功使用OpenFeign进行远程调用,使用默认的负载均衡算法(轮询算法)
服务消费方集成Sentinel
-
加入Sentinel依赖
<!--导入Sentinel依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
-
配置Sentinel
server: port: 80 servlet: context-path: /member-consumer spring: application: name: member-consumer cloud: nacos: discovery: #配置nacos服务注册中心的地址 server-addr: localhost:8848 #配置Sentinel sentinel: transport: dashboard: localhost:8080 port: 8719
-
使用postman调用接口,查看Sentinel Dashboard,验证Sentinel是否配置成功
OpenFeign整合Sentinel
-
配置OpenFeign整合Sentinel
server: port: 80 servlet: context-path: /member-consumer spring: application: name: member-consumer cloud: nacos: discovery: #配置nacos服务注册中心的地址 server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 feign: sentinel: #配置OpenFein整合Sentinel enabled: true
-
使用"@FeignClient"注解的"fallback"属性配置触发限流规则后使用的降级类
- 指定降级类为"MemberOpenFeignServiceFallback"
package anyi.space.memberConsumer.service; import anyi.sapce.common.entity.Member; import anyi.sapce.common.entity.ResponseResult; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.*; /** * @ProjectName: distributedSystemLearn * @FileName: MemberOpenFeignService * @Author: 杨逸 * @Data:2025/9/19 16:41 * @Description: 使用OpenFeign的接口 */ @Component //fallback指定降级类,当触发Sentinel的熔断降级策略时,使用fallback指定的实现类(这个类也要实现当前接口) @FeignClient(value = "member-service-provider",fallback = MemberOpenFeignServiceFallback.class) public interface MemberOpenFeignService { @GetMapping("/member-provider/member") ResponseResult<Member> getById(@RequestParam("id") Long id); @PostMapping("/member-provider/member") boolean save(@RequestBody Member member); }
-
创建一个降级类,实现"MemberOpenFeignServiceFallback"接口,当触发限流策略事,使用降级类进行响应处理
package anyi.space.memberConsumer.service; import anyi.sapce.common.entity.Member; import anyi.sapce.common.entity.ResponseResult; import org.springframework.stereotype.Component; /** * @ProjectName: distributedSystemLearn * @FileName: MemberOpenFeignServiceFallback * @Author: 杨逸 * @Data:2025/9/19 18:22 * @Description: Sentinel熔断降级后OpenFeign接口使用的实现类 */ @Component public class MemberOpenFeignServiceFallback implements MemberOpenFeignService{ @Override public ResponseResult<Member> getById(Long id) { return ResponseResult.errorResult(500,"触发Sentinel熔断降级策略,使用降级类,快速返回结果,避免服务雪崩"); } @Override public boolean save(Member member) { return false; } }
-
在Sentienl Dashboard中配置限流规则
- 将单机QPS设置为20,方便测试
-
使用jmeter进行压力测试,验证Sentinel整合OpenFeign是否成功,测试能否触发限流规则使用降级类进行响应处理
- 测试初,能够返回正确的结果
- 一段时间后,触发限流规则,返回降级的处理结果
Sentinel规则持久
目前Sentinel配置的规则,再重启后会丢失,我们希望配置的规则在每次启动服务时能够直接使用,所以我们需要规则持久化
规则持久化常见方式:
- 阿里云Ahas[最方便/付费],官方文档
- 在"Nacos Server"配置规则,完成持久化-官方推荐
- 将规则持久化到本地文件,定时同步
使用Nacos持久Sentienl配置规则的案例
-
在nacos配置Sentinel规则
-
新建一个配置,选择JSON格式,内容为一个对象数组
-
参考配置,一比一配置持久化
-
配置内容
[ { //资源名称 "resource":"GET:http://member-service-provider/member-provider/member", //调用来源 "limitApp":"default", //阈值模式 "grade":1, //阈值 "count":20, //流控模式 "strategy":0, //流控效果 "controlBehavior":0, //是否集群 "clusterMode":false } ]
-
参数详解
- resource∶资源名称
- limitApp∶来源应用
- grade∶阈值类型,0表示线程数,1表示QPS
- count∶单机阈值
- strategy∶流控模式,0表示直接,1表示关联,2表示链路
- controlBehavior∶流控效果,0表示快速失败,1表示WarmUp,2表示排队等待
- clusterMode∶是否集群
参数名 说明 取值与含义 resource 资源名称 被保护的具体资源标识 limitApp 来源应用 default-所有来源,或指定具体应用名 grade 阈值类型 0: 线程数(并发数)
1: QPS(每秒请求数)count 单机阈值 根据grade类型:
- 线程数:最大并发数
- QPS:每秒最大请求数strategy 流控模式 0: 直接(对资源本身限流)
1: 关联(关联资源触发)
2: 链路(入口链路限流)controlBehavior 流控效果 0: 快速失败(直接拒绝)
1: WarmUp(预热模式)
2: 排队等待(匀速通过)clusterMode 是否集群 true: 集群限流模式
false: 单机限流模式
-
-
在微服务中加入Sentinel整合Nacos持久化Sentinel限流规则的依赖
<!--导入Sentinel整合Nacos进行Sentinel规则持久化的依赖--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
-
在微服务中配置从Nacos拉取Sentinel的限流规则
server: port: 80 servlet: context-path: /member-consumer spring: application: name: member-consumer cloud: nacos: discovery: #配置nacos服务注册中心的地址 server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 datasource: #配置一个数据源,规则持久化的数据源 ds1: nacos: server-addr: localhost:8848 dataId: Sentinel-config.json groupId: SENTINEL_GROUP data-type: json rule-type: flow feign: sentinel: #配置OpenFein整合Sentinel enabled: true
-
重启服务验证持久化规则是否生效
-
重启服务,使用postman访问接口一次,可以看到Nacos中配置的规则出现Sentinel Dashboard中
-
使用jmeter进行压力测试,验证限流规则生效
-
-
1