• Spring Cloud Alibaba Sentinel 简单使用


    Sentinel 主要功能

    1. 流量控制: 可以通过配置规则对接口的访问进行限制, 避免因流量过高而导致的系统崩溃.
    2. 服务熔断: 当后端服务不可用或异常时, 可以通过配置熔断规则快速失败并返回错误信息, 避免连锁故障.
    3. 系统负载保护: 根据系统负载情况, 自动控制流量的通过, 防止系统出现过载.
    4. 统计和监控: 提供实时的流量控制和熔断信息统计

    Sentinel 作用

    1. 防止雪崩效应: 当访问发生异常时 Sentinel 可以通过熔断机制返回错误信息, 从而防止其他服务也出现故障.
    2. 流量控制: Sentinel 可以通过限流机制, 限制并发请求数量, 来防止突然出现大量请求导致系统过载.
    3. 保护系统稳定性: Sentinel 可以对请求进行限流和熔断, 避免某个服务发生故障从而导致系统不可用.

    常见的流量控制算法

    计数器算法

    计数器算法是在一定时间内记录请求次数, 当超过规定时间后计数器就会清零然后重新开始计算, 当请求超过间隔时间内最大次数时就会拒绝访问
    计数器算法的特点是: 实现简单, 但是存在"突刺现象"

    漏桶算法

    漏桶算法工作原理类似于一个底部有小孔的桶, 无论流入多少水, 流出的速度始终保持恒定. 当桶满时, 新的水会溢出, 即超出的流量被丢弃, 这种算法能够平滑突发流量, 常用于限制数据的传输速度
    漏桶算法提供了一种机制, 可以让突发流量被整形, 以便为系统提供稳定的请求. 如: Sentinel 中流量整形(匀速排队功能)
    image.png

    令牌桶算法

    令牌桶算法中, 存在一个固定大小的令牌桶, 该桶会以恒定的速率源源不断地产生令牌, 当系统需要发送数据时, 会从令牌桶中取出一个令牌, 如果桶中没有可用的令牌, 那么该次数据发送就会被限制, 这种机制确保了即使面临突发流量, 系统也能保持稳定运行
    image.png
    令牌桶算法和漏桶算法的区别是: 漏桶算法是匀速的处理请求, 而令牌桶算法可以应对突发的流量.

    Sentinel 流量控制

    image.png
    流量控制的三种效果:

    1. 快速失败: 当请求触发限流规则后, 会被理解拒绝并抛出 FlowException. 这种方法适用于对系统处理能力确切已知的情况.
    2. 排队等待(匀速通过): 排队等待会控制请求的间隔时间, 让请求稳定并且匀速通过. 这种方法可以用来处理间隔性突发的高流量.
    3. 冷启动(Warm Up): 该方法是通过让流量缓慢增加, 在一定时间内逐渐增加到阈值上限, 从而防止流量突然增加时, 直接把系统拉升到高水位可能瞬间把系统压垮.

    Sentinel 熔断

    image.png
    熔断策略也是三种:

    1. 慢调用比例: 在统计时长内, 如果时间超过最大RT则为慢请求, 如果慢请求超过阈值, 并且请求数量大于最小请求数, 就会触发熔断
    2. 异常比例: 在统计时长内, 如果请求的比例大于阈值, 并且请求数大于最小请求数, 就会触发熔断
    3. 异常数: 在统计时长内, 如果异常数大于阈值, 并且请求数大于最小请求数, 就会触发熔断

    Sentinel 基本使用

    添加依赖

    <dependency>
      <groupId>com.alibaba.cloudgroupId>
      <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    定义资源

    定义资源可通过代码或者注解实现
    通过代码方式定义资源:

    @RequestMapping("/getid")
    public String getId() {
        try(Entry entry = SphU.entry("getid")) { // 定义资源
            return "getId: " + new Random().nextInt(100);
        }catch (BlockException e) {
            // 当前资源已经被限流或熔断
            return "被限制";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    通过注解方式定义资源:

    @SentinelResource(value = "getname", blockHandler = "myBlockHandler")
        @RequestMapping("/getname")
        public String getName() throws InterruptedException {
            Thread.sleep(100);
            return "getName: " + new Random().nextInt(100);
        }
    
        /**
         * 限流之后的执行方法
         * @param blockException
         * @return
         */
        public String myBlockHandler(BlockException blockException) {
            if (blockException instanceof FlowException) {
                return "限流";
            } else if (blockException instanceof DegradeException) {
                return "熔断";
            }
            return "被限制";
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    注意:

    1. myBlockHandler 的返回类型和参数必须和绑定的方法一致, 否则就会报错
    2. myBlockHandler 必须包含 BlockException 参数

    @SentinelResource 属性说明

    • value: 资源名称(必填)
    • entryType: 资源调用的流量类型 EntryType.IN/EntryType.OUT(默认)
    • blockHandler: 限流或熔断执行时对应的方法
    • fallback/fallbackClass: 非 BlockException 时, 其他非限流/熔断是异常对应的方法
    • exceptionsToIgnore: 用于指定哪些异常被排除掉, 不好计入异常统计和 fallback 逻辑中

    定义限流规则

        private static void initFlowRules() {
            List<FlowRule> rules = new ArrayList<>();
            FlowRule rule = new FlowRule();
            rule.setResource("getname"); // 必须, 资源名
            rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 必须, 限流指标 QPS/线程数
            rule.setCount(1); // 必须, 限流数量: 上一步线程数或QPS值
            rules.add(rule);
            FlowRuleManager.loadRules(rules);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    定义熔断规则

        private static void initDegradeRules() {
            List<DegradeRule> rules = new ArrayList<>();
            DegradeRule rule = new DegradeRule();
            rule.setResource("getname"); // 资源名
            rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // 熔断策略
            rule.setCount(10); // RT值
            rule.setStatIntervalMs(1000); // 统计时长
            rule.setSlowRatioThreshold(0.5); // 阈值
            rule.setMinRequestAmount(1); // 最小请求数
            rule.setTimeWindow(5); // 熔断时长 单位为秒
            rules.add(rule);
            DegradeRuleManager.loadRules(rules);
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    如何判断熔断还是限流

    if (blockException instanceof FlowException) {
        return "限流";
    } else if (blockException instanceof DegradeException) {
        return "熔断";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    自定义 Sentinel 异常

    Sentinel 异常分为三种: 局部自定义异常, 全局自定义异常, 系统自定义异常

    局部自定义异常

        @SentinelResource(value = "getname", blockHandler = "myBlockHandler")
        @RequestMapping("/getname")
        public String getName() throws InterruptedException {
            Thread.sleep(100);
            return "getName: " + new Random().nextInt(100);
        }
    
        /**
         * 限流之后的执行方法
         * @param blockException
         * @return
         */
        public String myBlockHandler(BlockException blockException) {
            if (blockException instanceof FlowException) {
                return "限流";
            } else if (blockException instanceof DegradeException) {
                return "熔断";
            }
            return "被限制";
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    局部自定义异常只需要创建一个方法然后在@SentinelResource 中设置即可

    全局自定义异常

    import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
    import com.alibaba.csp.sentinel.slots.block.BlockException;
    import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
    import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
    import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
    import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    
    /**
     * 定义 Sentinel 全局自定义异常
     */
    @Component
    public class SentinelExceptionHandler implements BlockExceptionHandler {
        @Override
        public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
            int code = HttpStatus.TOO_MANY_REQUESTS.value();
            String msg = "未知异常";
            if (e instanceof FlowException) {
                msg = "被限流";
            } else if (e instanceof DegradeException) {
                msg = "被熔断";
            } else if (e instanceof ParamFlowException) {
                msg = "请求触发了热点限流";
            } else if (e instanceof AuthorityException) {
                code = HttpStatus.UNAUTHORIZED.value();
                msg = "暂无权限";
            }
            httpServletResponse.setContentType("application/json;charset=utf-8");
            httpServletResponse.setStatus(code);
            httpServletResponse.getWriter().println("{\"code\":" + code + ", \"msg\":\"" + msg +"\"}");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    全局自定义异常需要实现 BlockExceptionHandler 接口然后重写 handle 方法

    系统自定义异常

    如果只配置熔断和限流的情况下配置全局自定义异常即可, 但是配置一些特殊的规则如: 热点规则, 全局自定义异常就不会起作用, 就需要配置系统自定义异常

    import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    import java.util.HashMap;
    import java.util.Objects;
    
    /**
     * 自定义系统异常
     */
    @RestControllerAdvice
    public class SystemException {
        @ExceptionHandler(ParamFlowException.class)
        public HashMap<String, Object> ParamFlowException(ParamFlowException e) {
            return new HashMap<>(){{
                put("code", HttpStatus.TOO_MANY_REQUESTS.value());
                put("msg", "热点限流");
            }};
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    使用 Nacos 存储配置

    Sentinel 配置的规则默认存储到内存中, 会随着项目的重启而清空, 所以就需要对这些规则进行持久化配置

    引入依赖

    <dependency>
      <groupId>com.alibaba.cloudgroupId>
      <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
    dependency>
    <dependency>
      <groupId>com.alibaba.cspgroupId>
      <artifactId>sentinel-datasource-nacosartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    设置配置文件

    spring:
      application:
        name: sentinel-demo
      cloud:
        sentinel:
          transport:
            dashboard: localhost:18080
          datasource:
            ds:
              nacos:
                server-addr: localhost:8848
                username: nacos
                password: nacos
                data-id: ${spring.application.name}-flow-rules
                group-id: DEFAULT_GROUP
                data-type: json
                rule-type: flow
            ds2:
              nacos:
                server-addr: localhost:8848
                username: nacos
                password: nacos
                data-id: ${spring.application.name}-degrade-demo
                group-id: DEFAULT_GROUP
                data-type: json
                rule-type: degrade
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    image.png

    Nacos 中设置配置信息

    限流:

    [
      {
        "resource":"/user/getname",
        "limitApp":"default",
        "grade":1,
        "count":2,
        "strategy":0,
        "controlBehavior":0,
        "clusterMode":false
    
      },
      {
        "resource":"/user/getid",
        "limitApp":"default",
        "grade":1,
        "count":2,
        "strategy":0,
        "controlBehavior":0,
        "clusterMode":false
    
      }
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    image.png
    熔断:

    [{
        "resource":"/user/getname",
        "grade":0,
        "count":10,
        "timeWindow":5,
        "minRequestAmount":1,
        "statIntervalMs":1000,
        "slowRationThreshold":0.5
    }]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    image.png

  • 相关阅读:
    基于CycleGAN的山水风格画迁移
    【Pytorch】torch. matmul()
    《Jetpack Compose从入门到实战》 第二章 了解常用UI组件
    如何在Dev-c++中向项目初始源文件插入代码?
    MCS:多元随机变量——多项式分布
    204.计数质数 | 372.超级次方
    了解常用测试模型 -- V模型、W模型
    管理类联考——英语二——记忆篇——按题型记忆必要单词
    哪种烧录单片机的方法合适?
    java根据经纬度转地址或者根据地址转经纬度
  • 原文地址:https://blog.csdn.net/m0_71645055/article/details/134542035