Spring Cloud Alibaba Sentinel使用教程

什么是Sentinel

Sentinel的github官网

Sentinel官网

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。

Sentinel以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

分布式系统的流量防卫兵,保护你的微服务

Sentinel的核心概念

流量监控

每个微服务能承载的流量是有限的,Sentinel能控制访问微服务的流量,当流量过大时,会进行限流,保证服务不会被压垮

熔断降级

微服务中会出现因网络等出现某个链路节点不可用的状态,在这个服务请求链路就会在这里卡住,当大量的连接堆积,就会占用服务器的资源,达到一定程度,服务就会崩溃

熔断降级就是,通过对链路进行监控,当某个链路节点出现故障时,让访问这个节点服务进行快速失败或稍后重试等机制,从而保护系统资源不会因某个链路节点的故障导致耗尽

某个节点的故障会导致整个链路的故障,这个链路的节点又被多个服务使用,所以一个链路节点的故障可能会导致整个系统的崩溃,熔断降级就是解决这个问题的机制

系统负载保护

根据系统能够处理的请求,和允许进来的请求,来做平衡,追求的目标是在系统不被拖垮的情况下,提高系统的吞吐率

消息削峰填谷

某瞬时来了大流量的请求,而如果此时要处理所有请求,很可能会导致系统负载过高,影响稳定性。但其实可能后面几秒之内都没有消息投递,若直接把多余的消息丢掉则没有充分利用系统处理消息的能力

Sentinel的RateLimiter模式能在某一段时间间隔内以匀速方式处理这样的请求,充分利用系统的处理能力,也就是削峰填谷,保证资源的稳定性

Sentinel两大组成部分

  1. 核心库
    • (Java客户端)不依赖任何框架/库,能够运行在所有Java运行时环境,对SpringCloud有较好的支持
  2. 仪表盘
    • (Dashboard)基于SpringBoot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器

搭建Sentinel仪表盘服务

搭建Sentinel仪表盘监控各个微服务的使用情况

  1. 下载Sentinel Dashboard

  2. 运行Sentinel Dashboard

    • 切换到对应的目录,使用java -jar sentinel-dashboard-1.8.8.jar命令运行
    • 默认使用的端口是8080,可以通过参数server.port指定运行的端口java -jar sentinel-dashboard-1.8.8.jar --server.port=8888

  3. 通过浏览器访问Sentinel Dashboard

将Sentinel客户端集成到微服务模块中

  1. 在微服务模块中加入Sentinel依赖

            <!--引入Sentinel客户端依赖-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>
    
  2. 在微服务模块中配置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
    
  3. 运行微服务,然后通过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的限流

  1. 在Sentinel控制台的"簇点链路"选项中选中一个资源,点击流控进行添加流控规则

  2. 对资源进行QPS限流,QPS大小设置为1

  3. 在控制台中的流控规则中可以看到新增的流控规则

  4. 通过postman测试流控是否成功

    • 正常访问,请求成功,能够获取数据

    • 异常高频访问,请求失败,接口成功被限流

  5. 1

基于QPS的关联流控案例

案例:当关联资源"t2"的QPS大于1时,限流资源"t1"

  1. 对资源"t1"添加关联流控规则,关联资源为"t2"

  2. 在流控规则中查看新增的规则

  3. 使用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"接口的熔断,熔断时间为十秒
  1. 在"簇点链路"选项中,找到对应的接口,点击熔断进行熔断规则添加

  2. 配置对应的熔断规则

    • 熔断策略选择慢调用比例
    • 最大RT(Request Time)设置为200ms
    • 触发熔断的比例阈值设置50%
    • 熔断时间设置为10s
    • 触发熔断统计最小请求数设置为5
    • 统计时长设置1000ms

  3. 添加成功后,可以在熔断规则中查看到新增的熔断规则

  4. 在"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;
        }
    }
    
    
  5. 使用postman调用"t3"接口,模拟并发,验证熔断机制是否触发

    • 使用postman模拟20个用户并发调用"t3"接口,持续十分钟

    • 然后在"t3"模拟并发时,测试"t3"接口,验证触发熔断成功

    • 模拟并发结束后,等待十秒的熔断回复期过后,再次访问"t3"接口,验证熔断已恢复

基于异常比例的熔断策略案例

  • 需求:
    • 在统计时间内,请求数大于等于5,接口出现错误的比例大于等于{50\%},则触发熔断机制
  1. 配置熔断策略

    • 熔断策略使用异常比例
    • 触发阈值设置50%
    • 熔断时间设置为5s
    • 触发熔断统计最小请求数设置为5
    • 统计时长设置1000ms

  2. 在"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;
        }
    }
    
    
  3. 使用postman模拟并发调用"t4"接口,验证熔断策略是否生效

    • 正常访问,服务每隔一次调用出现一次错误

    • 模拟并发访问后,再访问接口,接口被熔断

    • 等待熔断时间过后,再次访问,接口熔断被恢复

基于异常数的熔断策略案例

  • 需求
    • 在统计时间内,接口出现错误的次数大于等于10时,则触发熔断策略
  1. 配置基于异常数的熔断策略

  2. 在"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;
        }
    }
    
    
  3. 使用postman验证熔断机制是否生效

    • 开始调用时,"t5"接口访问成功,并出现错误

    • 调用"t5"接口出现错误一段时间后,触发熔断策略,"t5"接口的访问被限制

Sentinel的热点规则

官方文档

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。

热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效

热点规则案例

  • 需求
    • 对资源"news"配置热点规则,当资源出现并发,触发热点规则,修改返回信息提示信息,模拟使用了特定逻辑处理过了热点资源
  • 场景
    • 对资源"news"配置热点规则,当资源出现并发,触发热点规则,我们可以对热点资源进行特定的处理,比如:直接限流熔断,或者将热点资源缓存起来等等
  1. 在为服务中定义"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;
        }
    }
    
  2. 在Sentinel Dashboard中配置"/news"接口的热点规则

    • 资源名配置为"news",与微服务中"@SentinelResource"注解中的value值保持一直
    • 参数索引设置为0,表示统计"/news"接口第一个参数
    • 单机阈值设置为10,统计窗口设置为1,表示在一秒内该热点被访问十次,则触发热点规则

  3. 在热点规则选项中可以看到新增的热点规则

  4. 使用postman模拟并发调用"news"接口,验证是否成功触发热点规则

    • 刚开始调用"/news"接口成功

    • 并发后触发热点规则,返回了热点规则触发后的处理信息

  5. 配置热点参数,在热点规则的基础上设置热点参数,对特定参数的资源进行特殊处理

    当请求的参数配置热点参数则统计进热点参数的规则,否则统计进行热点规则

    • 在热点规则中找到编辑按钮

    • 打开高级选项,进行热点参数配置

    • 参数类型设置为"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时(方便测试),触发系统保护规则,系统被阻塞保护
  1. 配置系统保护规则

    • 选择"入口QPS"类型
    • 触发阈值设置为2

  2. 在系统规则选项中可以查看新增的系统保护规则

  3. 使用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

  1. 新增一个"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;
        }
    
    }
    
    
  2. 创建全局阻塞处理类,并编写对应的阻塞处理方法

    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;
        }
    }
    
    
  3. 在Sentinel Dashboard中配置"t6"的限流规则

    • 配置QPS为2,方便测试验证

  4. 使用postman验证自定义全局限流处理类是否生效

    • 正常调用返回正确的结果

    • 并发调用后返回blackHandler处理后的结果

fallback

  • blockHandler只负责sentine控制台配置违规
  • fallback负责Java异常/业务异常

使用"fallback"处理出现业务异常的案例

  • 需求
    • 当"t7"出现业务异常时,通过"@SentinelResource"注解的"fallback"属性指定对应的处理方法
  1. 新增"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;
        }
    
    }
    
    
  2. 创建一个业务异常处理类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;
        }
    }
    
    
  3. 使用postman测试"t7"接口,验证自定义业务处理方法是否生效

    • 开始访问时,结果正常

    • 多次访问后出现,异常处理的结果

Sentinel + OpenFeign + Nacos整合

服务消费方集成Openfeign

  1. 在服务消费方导入Openfeign依赖

            <!--导入OpenFeign的依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
                <version>3.1.5</version>
            </dependency>
    
  2. 编写服务提供方对应接口的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);
    }
    
    
  3. 在服务消费方使用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);
        }
    }
    
    
  4. 在启动类使用"@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);
        }
    }
    
    
  5. 使用postman测试接口

    • 成功使用OpenFeign进行远程调用,使用默认的负载均衡算法(轮询算法)

服务消费方集成Sentinel

  1. 加入Sentinel依赖

            <!--导入Sentinel依赖-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>
    
  2. 配置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
    
    
  3. 使用postman调用接口,查看Sentinel Dashboard,验证Sentinel是否配置成功

OpenFeign整合Sentinel

  1. 配置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
    
    
  2. 使用"@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);
    }
    
    
  3. 创建一个降级类,实现"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;
        }
    }
    
    
  4. 在Sentienl Dashboard中配置限流规则

    • 将单机QPS设置为20,方便测试

  5. 使用jmeter进行压力测试,验证Sentinel整合OpenFeign是否成功,测试能否触发限流规则使用降级类进行响应处理

    • 测试初,能够返回正确的结果

    • 一段时间后,触发限流规则,返回降级的处理结果

Sentinel规则持久

目前Sentinel配置的规则,再重启后会丢失,我们希望配置的规则在每次启动服务时能够直接使用,所以我们需要规则持久化

规则持久化常见方式:

  • 阿里云Ahas[最方便/付费],官方文档
  • 在"Nacos Server"配置规则,完成持久化-官方推荐
  • 将规则持久化到本地文件,定时同步

使用Nacos持久Sentienl配置规则的案例

  1. 在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: 单机限流模式
  2. 在微服务中加入Sentinel整合Nacos持久化Sentinel限流规则的依赖

            <!--导入Sentinel整合Nacos进行Sentinel规则持久化的依赖-->
            <dependency>
                <groupId>com.alibaba.csp</groupId>
                <artifactId>sentinel-datasource-nacos</artifactId>
            </dependency>
    
  3. 在微服务中配置从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
    
    
  4. 重启服务验证持久化规则是否生效

    • 重启服务,使用postman访问接口一次,可以看到Nacos中配置的规则出现Sentinel Dashboard中

    • 使用jmeter进行压力测试,验证限流规则生效

  5. 1