• SpringCloud微服务(十一)——Sentinel服务熔断限流


    SpringCloud Alibaba Sentinel服务熔断与限流

    简介

    github:[https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5](https://github.com/alibaba/Sentinel/wiki/如何使用)

    官网:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel

    一句话解释,跟Hystrix一样的理念

    随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护等多个维度来帮助您保障微服务的稳定性。

    在这里插入图片描述

    流量控制

    流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。

    熔断降级

    除了流量控制以外,及时对调用链路中的不稳定因素进行熔断也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,可能会导致请求发生堆积,进而导致级联错误。

    系统负载保护

    Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。

    针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

    安装

    下载:https://github.com/alibaba/Sentinel/releases

    下载的是sentinel-dashboard-1.7.1.jar

    #默认8080端口,8080不要被占用
    #直接运行即可,需要jdk环境
    java -jar sentinel-dashboard-1.7.1.jar
    
    #linux系统
    nohup java -jar sentinel-dashboard-1.7.1.jar &
    ctrl+c
    cat nohup.out
    #访问8080端口
    #账号sentinel,密码sentinel
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    演示工程

    依赖配置

    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    
    
    <dependency>
        <groupId>com.alibaba.cspgroupId>
        <artifactId>sentinel-datasource-nacosartifactId>
    dependency>
    
    
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    yml配置

    server:
      port: 8401
    
    spring:
      application:
        name: cloudalibaba-sentinel-service
      cloud:
        nacos:
          discovery:
            # Nacos服务注册中心地址
            server-addr: localhost:8848
        sentinel:
          transport:
            # sentinel dashboard 地址
            dashboard: 192.168.169.130:8080
            # 默认为8719,如果被占用会自动+1,直到找到为止
            # 簇点链路,可以把某些微服务归类到某个链路,统一处理
            port: 8719
            
          # 流控规则持久化到nacos
          datasource:
            dsl:
              nacos:
                server-addr: localhost:8848
                data-id: ${spring.application.name}
                group-id: DEFAULT_GROUP
                data-type: json
                rule-type: flow
                
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    
    • 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

    启动类使用的是nacos注册中心@EnableDiscoveryClient

    启动,访问Rest请求,可以在sentinel管理页面看到确实有监控cloudalibaba-sentinel-service微服务:

    如果不访问该微服务的rest请求或者长时间没有访问该微服务,sentinel管理页面会去掉对该微服务接口的监控。要是页面找不到就访问下资源接口。

    在这里插入图片描述

    流控规则

    流量控制

    就是限流,限制资源接口的访问数

    官网:https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6

    • 资源名:唯一名称,默认请求路径,就是restcontroller上的路径
    • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
    • 阈值类型/单机阈值
      • QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流
      • 线程数:当调用该api的线程数达到阈值的时候,进行限流
      • 2者区别就是QPS会让请求全部过来访问,线程数最多只能过来对应线程数数量的请求数,来多了也没用
    • 是否集群:不需要集群
    • 流控模式:
      • 直接:api达到限流条件,直接限流
      • 关联:当关联的资源达到阈值时,就限流自己
      • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)(api级别的针对来源)
    • 流控效果:
      • 快速失败:直接失败,抛异常
      • Warm Up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
      • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效
    流控模式
        @GetMapping("/testA")
        public String testA(){
            try {
                //0.8秒
                TimeUnit.MILLISECONDS.sleep(800);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "testA-----";
        }
    
        @GetMapping("/testB")
        public String testB(){
            log.info(Thread.currentThread().getName() + "...testB ");
            return "testB   -----";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    直接(默认)

    系统默认直接快速失败,超过阈值直接限流

    阈值类型:QPS或线程数

    如下设置路径为/testA的流控:

    单机阈值是每秒1个访问/每秒一个线程数

    在这里插入图片描述

    快速点击访问http://localhost:8401/testA

    手速超过每秒1个访问/每秒一个线程数,就会限制访问。

    在这里插入图片描述

    关联

    当关联的资源达到阈值时,就限流自己

    当与A关联的资源B达到阈值后,就限流自己

    举例子:支付接口遭到大量访问,且很多未处理,那么我们应该限制下订单的接口访问,不然支付接口访问会堆积更多,支付接口会挂。

    当关联资源/testB的QPS阈值超过1时,就限流/testA的Rest访问地址,当关联资源到达阈值后限制配置好的的资源名。

    在这里插入图片描述

    postman模拟并发密集访问testB,jmeter也行。

    在这里插入图片描述
    在这里插入图片描述

    这样就可以大量访问/testB,在网页访问/testA,将无法访问,被限流

    在这里插入图片描述

    链路

    多个请求调用了同一个微服务

    簇点链路端口默认为8719,如果被占用会自动+1,直到找到为止

    簇点链路,可以把某些微服务归类到某个链路端口,统一处理

    NodeSelectorSlot 中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root 的虚拟节点,调用链的入口都是这个虚节点的子节点。

    一棵典型的调用树如下图所示:

         	          machine-root
                        /       \
                       /         \
                 Entrance1     Entrance2
                    /             \
                   /               \
          DefaultNode(nodeA)   DefaultNode(nodeA)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上图中来自入口 Entrance1Entrance2 的请求都调用到了资源 NodeA,Sentinel 允许只根据某个入口的统计信息对资源限流。比如我们可以设置 FlowRule.strategyRuleConstant.CHAIN,同时设置 FlowRule.ref_identityEntrance1 来表示只有从入口 Entrance1 的调用才会记录到 NodeA 的限流统计当中,而不关心经 Entrance2 到来的调用。

    调用链的入口(上下文)是通过 API 方法 ContextUtil.enter(contextName) 定义的,其中 contextName 即对应调用链路入口名称。

    流控效果
    直接(默认)

    快速失败(默认的流控处理)

    直接失败,抛出异常

    Blocked by Sentinel(flow limiting)页面

    源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

    预热

    公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值

    默认coldFactor为3,即请求QPS从threshold/3开始,经预热时长逐渐升至设定的QPS阈值

    限流冷启动:https://github.com/alibaba/Sentinel/wiki/

    在这里插入图片描述

    系统初始化的阈值为10/3约等于3,即阈值刚开始为3;然后过了5秒后阈值才慢慢升高恢复到10。

    多次点击http://localhost:8401/testB

    一开始点太快会限流Blocked by Sentinel(flow limiting),5秒后10阈值,每秒10个内都可以访问。

    应用场景:秒杀系统在开启的瞬间,会有大量流量上来,很有可能把系统打死,预热方式就是为了保护系统,可慢慢把流量放进来,慢慢的把阈值增长到设置的阈值。

    排队等待

    匀速排队,阈值必须设置为QPS

    源码:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

    让请求均匀的速度通过,阈值必须设置为QPS,否则无效

    如下,/testA每秒1次请求,超过的话排队等待,等待的超时时间为20000毫秒。

    在这里插入图片描述

    匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考 流量控制 - 匀速器模式,具体的例子可以参见 PaceFlowDemo

    这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

    postman模拟并发密集访问testA

    controller打印日志

        @GetMapping("/testA")
        public String testA(){
            log.info(Thread.currentThread().getName() + "...testA ");
            return "testA   -----";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    每秒一个:

    在这里插入图片描述

    降级规则

    服务降级,服务超时或异常给出友好提示或兜底方案,实际开发需要自定义返回,这里先测试。

    官网:https://github.com/alibaba/Sentinel/wiki/熔断降级

    Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。

    当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认抛出DegradeException)

    Sentinel的断路器是没有半开状态的,半开的状态系统自动去检测是否请求异常, 没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用.具体可以参考Hystrix。

    • RT(平均响应时间,秒级)

      平均响应时间:超出阈值在时间窗口内通过的请求>=5,有两个条件同时满足后触发降级

      窗口期过后关闭断路器

      RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

    • 异常比例(秒级)

      QPS>=5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

    • 异常数(分钟级)

      异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

    降级策略实战
    RT

    平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。

    在这里插入图片描述

    对controller睡眠1秒测试TimeUnit.SECONDS.sleep(1);,对资源选择降级设置:(条件必须每秒请求超过5个,且超过阈值)

    在这里插入图片描述

    使用jmeter压力测试

    在这里插入图片描述
    在这里插入图片描述

    执行,一秒10个请求>5,请求均超过阈值200ms,未来的时间窗口被降级,jmeter一直执行,一直降级,关闭jmeter,即可恢复。

    异常比例

    异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

    在这里插入图片描述

    在对应controller加上异常int age = 10 /0 ;测试,访问,如下:

    在这里插入图片描述

    条件和设置:

    在这里插入图片描述

    jmeter高并发测试,再次网页访问,不是上次的报错页面了,而是降级:

    在这里插入图片描述

    异常数

    异常数是按分钟统计的

    异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

    在这里插入图片描述

    在对应controller加上异常int age = 10 /0 ;测试,访问,如下:

    在这里插入图片描述

    接着设置异常数降级规则:

    在这里插入图片描述

    然后网页访问请求,前5次都是报错的页面,第6次后都是降级页面,降级页面会持续70秒,70秒过后恢复。

    在这里插入图片描述

    热点规则

    也是限流措施,热点key,对某个访问量高的参数值进行限流。

    官网:https://github.com/alibaba/Sentinel/wiki/热点参数限流

    何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

    • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
    • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

    热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

    在这里插入图片描述

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

    兜底方案:

    分为系统默认和客户自定义,两种

    之前的case,限流出问题后,都是sentinel系统默认的提示:Blocked by Sentinel(flow limiting)

    我们也可以自定义,类似hystrix的fallback降级兜底方法,结论是从@HystrixCommand—>>>@SentinelResource

    源码:com.alibaba.csp.sentinel.slots.block.BlockException

    默认是出错就抛异常BlockException处理。

    普通设置

    测试代码@RestController:

    @SentinelResource(value = "testHotKey", blockHandler = "dealTestHotKey")

    设置热点key的异常方法,就是热点key被触发限流了就跳到dealTestHotKey方法。通过BlockException异常来处理。

        @GetMapping("/testHotKey")
        @SentinelResource(value = "testHotKey", blockHandler = "dealTestHotKey") //value自定义,唯一就行,尽量保持跟路径一致
        public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                                 @RequestParam(value = "p2", required = false) String p2){
            //int age = 10 /0;
            return "testHotKey -----";
        }
    
        //BlockException blockException必须加,兜底方法
        public String dealTestHotKey(String p1, String p2, BlockException blockException){
            return "dealTestHotKey---------";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    sentinel上设置热点key:

    testHotKey是注解上的名字,保持一致。

    在这里插入图片描述

    索引0开始,对应资源接口上的参数,也就是p1。

    只要参数中没有p1带参,无论怎么访问都是没问题的。

    在这里插入图片描述

    如果带参p1,只要符合,不超过阈值也可以正常响应,如果1s内QPS访问次数超过阈值1时,则报错。当然这是自定义的报错页面,因为我们加了blockHandler = "dealTestHotKey"方法

    在这里插入图片描述

    去掉blockHandler = "dealTestHotKey"方法,则是如下页面

    在这里插入图片描述

    不是Blocked by Sentinel(flow limiting),热点key只处理Sentinel页面上的规则问题,如果代码加入int age = 10/0;等模拟异常代码,错误信息会直接在页面显示,不处理其他异常的降级,只处理这个BlockException异常,所以热点key必须加上兜底方法。

    @SentinelResource处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;

    int age = 10/0;这个是java运行报出的运行时异常RunTimeException.@SentinelResource不管

    @SentinelResource主管配置出错,运行出错自己走异常。

    参数额外项

    上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流

    我们期望p1参数当它是某个特殊值时,它的限流和平时不一样

    比如:特例:假如当p1的值等于5时,它的阈值可以达到200

    如下设置:

    参数类型对应rest接口的p1参数类型

    在这里插入图片描述

    当p1=5时,连续快速测试访问都没触发降级,当p1不等于5的时候,阈值变为平常的1。当p1等于5的时候,阈值变为200。

    在这里插入图片描述

    系统规则

    说白了就是这个系统规则会对所有rest接口生效。全局

    官网:https://github.com/alibaba/Sentinel/wiki/系统自适应限流

    Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

    • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5
    • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。0%-100%。
    • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
    • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
    • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

    在这里插入图片描述

    比如选择入口QPS的阈值为1,则所有的请求都是每秒只能请求一次,不然就降级。

    @SentinelResource

    按资源名称+后续处理

    测试自带的blockHandler异常方法

    @RestController
    public class RateLimitController {
    
        @GetMapping("/byResource")
        @SentinelResource(value = "byResource", blockHandler = "handleException")//名字可自定义,不唯一就行
        public CommonResult byResource(){
            return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020L, IdUtil.simpleUUID()));//hutool的工具包,生成UUID不带-
        }
        
        public CommonResult handleException(BlockException blockException){
            // 打印哪个异常方法在限流处理
            return new CommonResult<>(444, blockException.getClass().getCanonicalName()+"\t服务不可用" );
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    用@SentinelResource上的名字byResource设置流控规则,才会调到自定义方法,如果超过sentinel控制台设置的阈值,跳到自定义的方法:

    在这里插入图片描述

    按照Url地址限流+后续处理
        @GetMapping("/rateLimit/byUrl")
        @SentinelResource(value = "byUrl")
        public CommonResult byUrl(){
            return new CommonResult(200, "by url限流测试OK", new Payment(2020L, IdUtil.simpleUUID()));
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    通过访问的URL限流,会返回Sentinel自带默认的限流处理信息

    通过路径url设置流控规则,超过阈值:

    在这里插入图片描述

    上面兜底方案面临的问题

    • 系统默认的,没有体现我们自己的业务要求
    • 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观
    • 每个业务方法都添加一个兜底的,那代码膨胀
    • 全局统一的处理方法没有体现
    客户自定义限流处理逻辑

    实际开发常用

    这是争对sentinel控制规则违规的兜底,程序异常不行

    自定义限流处理类CustomerBlockHandler,参数必须得加上BlockException

    package com.wzq.springcloud.myhandler;
    
    import com.alibaba.csp.sentinel.slots.block.BlockException;
    import com.wzq.springcloud.entities.CommonResult;
    
    /**
     * 自定义全局异常方法,方法可被不同的接口使用
     * @author wzq
     * @version 1.0
     * @create 2020/03/06
     */
    public class CustomerBlockHandler {
    
        public static CommonResult handlerException(BlockException exception) {
            return new CommonResult(444, "客户自定义,global handlerException---1");
        }
    
        public static CommonResult handlerException2(BlockException exception) {
            return new CommonResult(444, "客户自定义,global handlerException---2");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    contoller调用

        //CustomerBlockHandler
    
        @GetMapping("/rateLimit/customerBlockHandler")
        @SentinelResource(value = "customerBlockHandler",
                blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")
        public CommonResult customerBlockHandler(){
            return new CommonResult(200, "客户自定义 限流测试OK", new Payment(2020L, IdUtil.simpleUUID()));
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")指定自定义限流类中的某个方法来处理。方法可多次被不用controller调用,解耦。

    超过流控阈值:

    在这里插入图片描述

    注意点

    注意:注解方式埋点不支持 private 方法。

    @SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

    • value:资源名称,必需项(不能为空)

    • entryType:entry 类型,可选项(默认为 EntryType.OUT

    • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

    • fallback

      :fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore

      里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:

      • 返回值类型必须与原函数返回值类型一致;
      • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
      • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
    • defaultFallback

      (since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:

      • 返回值类型必须与原函数返回值类型一致;
      • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
      • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
    • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

    注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理

    特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。

    Sentinel主要有三个核心Api:SphU定义资源、Tracer定义统计、ContextUtil定义了上下文

    服务熔断

    提供者9003/9004,设置2个一样的微服务9003/9004

    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    server:
      port: 9003/9004
    
    spring:
      application:
        name: nacos-payment-provider
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
    
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    启动类:@EnableDiscoveryClient

    controller接口:

    @RestController
    public class PaymentController {
    
        @Value("${server.port}")
        private String serverPort;
    
        public static Map<Long , Payment> hashMap = new HashMap<>();
        
        static {
            hashMap.put(1L, new Payment(1L, IdUtil.simpleUUID()));
            hashMap.put(2L, new Payment(2L, IdUtil.simpleUUID()));
            hashMap.put(3L, new Payment(3L, IdUtil.simpleUUID()));
        }
    
        @GetMapping("/paymentSQL/{id}")
        public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id{
            Payment payment = hashMap.get(id);
            return new CommonResult<>(200, "from mysql,serverPort:" + serverPort, payment);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    消费者84

            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
            dependency>
            
            <dependency>
                <groupId>com.alibaba.cspgroupId>
                <artifactId>sentinel-datasource-nacosartifactId>
            dependency>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
            dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    server:
      port: 84
    spring:
      application:
        name: nacos-order-consumer
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
        sentinel:
          transport:
            dashboard: 192.168.169.130:8080
            port: 8719
    
    service-url:
      nacos-user-service: http://nacos-payment-provider
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    启动类:@EnableDiscoveryClient

    Ribbon系列

    负载均衡

    消费者84添加rest负载均衡:

    @Configuration
    public class ApplicationContextConfig {
    
        @Bean
        @LoadBalanced
        public RestTemplate getRestTemplate(){
            return new RestTemplate();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通过服务名调用方法后实现轮询。

    无配置

    controller调用提供者接口:

    @RestController
    @Slf4j
    public class CircleBreakerController {
        
        private static final String SERVICE_URL = "http://nacos-payment-provider";
    
        @Resource
        private RestTemplate restTemplate;
    
        @RequestMapping("/consumer/fallback/{id}")
        @SentinelResource(value = "fallback") //没有配置
        public CommonResult<Payment> fallback(@PathVariable("id") Long id){
            CommonResult<Payment> commonResult = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class);
            if(id == 4){
                throw new IllegalArgumentException("IllegalArgumentException,非法参数异常");
            }else if(commonResult.getData() == null){
                throw new NullPointerException("NullPointerException,该ID没有记录,空指针异常");
            }
            return commonResult;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    @SentinelResource(value = "fallback") //没有配置

    降级都没配置,默认访问,采用ribbon轮询负载均衡:

    在这里插入图片描述

    id = 4,测试程序异常,error页面:

    在这里插入图片描述

    id = 5,测试程序异常,error页面:

    在这里插入图片描述

    只配置程序异常fallback处理
        @RequestMapping("/consumer/fallback/{id}")
        @SentinelResource(value = "fallback",fallback = "handlerFallback") //配置了fallback的,fallback只负责业务异常
        public CommonResult<Payment> fallback(@PathVariable("id") Long id){
            CommonResult<Payment> commonResult = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class);
            if(id == 4){
                throw new IllegalArgumentException("IllegalArgumentException,非法参数异常");
            }else if(commonResult.getData() == null){
                throw new NullPointerException("NullPointerException,该ID没有记录,空指针异常");
            }
            return commonResult;
        }
    
        // 本例是fallback
        public CommonResult handlerFallback(Long id, Throwable e){
            Payment payment = new Payment(id, null);
            return new CommonResult(444, "兜底异常handler,exception内容"+e.getMessage(), payment);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    @SentinelResource(value = "fallback",fallback = "handlerFallback") //配置了fallback的,fallback只负责业务异常

    fallback只处理程序异常的兜底,出错则跳到handlerFallback方法,测试程序异常,不再是error页面:

    在这里插入图片描述

    只配置sentinel控制台违规异常blockHandler处理
        @RequestMapping("/consumer/fallback/{id}") 
        @SentinelResource(value = "fallback",blockHandler = "blockHandler") 配置了blockHandler,只负责sentinel控制台配置违规 
        public CommonResult<Payment> fallback(@PathVariable("id") Long id){
            CommonResult<Payment> commonResult = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class);
            if(id == 4){
                throw new IllegalArgumentException("IllegalArgumentException,非法参数异常");
            }else if(commonResult.getData() == null){
                throw new NullPointerException("NullPointerException,该ID没有记录,空指针异常");
            }
            return commonResult;
        }
    
        public CommonResult blockHandler(Long id, BlockException exception){
            Payment payment = new Payment(id, null);
            return new CommonResult<>(445, "blockHandler-sentinel 限流,无此流水号:blockException" + exception.getMessage(), payment);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    @SentinelResource(value = "fallback",blockHandler = "blockHandler") 配置了blockHandler,只负责sentinel控制台配置违规

    blockHandler只处理sentinel控制台中配置的限流规则违规异常,先配置测试限流,名字是fallback那个:

    在这里插入图片描述

    前2次访问还是error页面,之后访问就触发了blockHandler兜底方法:

    在这里插入图片描述

    fallback和blockHandler都配置
        @RequestMapping("/consumer/fallback/{id}")
        @SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler")// 配置了blockHandler和fallback
        public CommonResult<Payment> fallback(@PathVariable("id") Long id){
            CommonResult<Payment> commonResult = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class);
            if(id == 4){
                throw new IllegalArgumentException("IllegalArgumentException,非法参数异常");
            }else if(commonResult.getData() == null){
                throw new NullPointerException("NullPointerException,该ID没有记录,空指针异常");
            }
            return commonResult;
        }
    
        // 本例是fallback
        public CommonResult handlerFallback(Long id, Throwable e){
            Payment payment = new Payment(id, null);
            return new CommonResult(444, "兜底异常handler,exception内容"+e.getMessage(), payment);
        }
    
        // blockHandler
        public CommonResult blockHandler(Long id, BlockException exception){
            Payment payment = new Payment(id, null);
            return new CommonResult<>(445, "blockHandler-sentinel 限流,无此流水号:blockException" + exception.getMessage(), payment);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    @SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler")// 配置了blockHandler和fallback

    配置了blockHandler和fallback

    设置流控规则:

    在这里插入图片描述

    1秒一个正常访问,但是超过阈值,调用blockHandler方法:

    在这里插入图片描述

    程序异常测试,调用fallback方法:

    在这里插入图片描述

    但是访问次数超过阈值,一样还会报blockHandler方法:

    在这里插入图片描述

    若blockHandler和fallback都进行了配置,则被限流而抛出BlockException时只会进入blockHandler处理逻辑。

    忽略属性
    @SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler",exceptionsToIgnore = {IllegalArgumentException.class}) // 配置了blockHandler和fallback
    
    
    • 1
    • 2

    exceptionsToIgnore={异常1,异常2,…}

    这样可以忽略掉某些程序异常,就是这些忽略的异常报错了,不会跳到fallback兜底方法,正常error页面。

    Feign系列

    新建提供者的feign模块,只需一个模块

    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    直接写接口,无需配置和启动类:

    接口跟服务提供者的controller一样

    /**
     * @author wzq
     * @version 1.0
     * @date 2020/03/07
     */
    @FeignClient(value = "nacos-payment-provider", fallback = PaymentFallback.class)
    public interface PaymentFeign {
    
        @GetMapping("/paymentSQL/{id}")
        CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Fallback熔断实现,对应该接口的方法:

    /**
     * @author wzq
     * @version 1.0
     * @date 2020/03/07
     */
    @Component
    public class PaymentFallback implements PaymentFeign {
        
        // 熔断
        @Override
        public CommonResult<Payment> paymentSQL(Long id) {
            return new CommonResult<>(444, "fallback");
        }
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    服务消费者:

    引入需要feign接口的模块依赖

    启动类加@EnableFeignClients

    yml配置加:

    #激活sentinel对feign的支持
    feign:  
      sentinel:    
        enabled: true
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    contoller调用:

        @Resource
        private PaymentFeign paymentFeign;
    
        // 直接调用feign
        @GetMapping("/consumer/paymentSQL/{id}")
        public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id{
            return paymentFeign.paymentSQL(id);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这样接口报错(程序错误和控制台违规)就会调用PaymentFallback的降级熔断方法

    测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级,不会被耗死

    框架比较:

    SentinelHystrixresilience4j
    隔离策略信号量隔离(并发线程数限流)线程池隔离/信号量隔离信号量隔离
    熔断降级策略基于响应时间、异常比率、异常数基于异常比率基于异常比率、响应时间
    实时统计实现滑动窗口(LeapArray)滑动窗口(基于RxJava)Ring Bit Buffer
    动态规则配置支持多种数据源支持多种数据源有限支持
    扩展性多个扩展性插件的形式接口的形式
    基于注解的支持支持支持支持
    限流基于QPS,支持基于调用关系的规范有限的支持Rate Limiter

    规则持久化

    不配每次重启微服务,之前在sentinel控制台上设置的规则就会不见。

    一旦我们重启应用,sentinel规则消失,生产环境需要将配置规则进行持久化

    将限流规则持久进Nacos保存,只要刷新微服务某个rest地址,sentinel控制台的流控规则就能看得到,只要Nacos里面的配置不删除,针对8401上的流控规则持续有效。

    
    <dependency>
        <groupId>com.alibaba.cspgroupId>
        <artifactId>sentinel-datasource-nacosartifactId>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    spring:
      application:
        name: cloudalibaba-sentinel-service
      cloud:
        nacos:
          discovery:
            # Nacos服务注册中心地址
            server-addr: localhost:8848
        sentinel:
          transport:
            # sentinel dashboard 地址
            dashboard: 192.168.169.130:8080
            # 默认为8719,如果被占用会自动+1,直到找到为止
            port: 8719
          # 流控规则持久化到nacos
          datasource:
            dsl:
              nacos:
                server-addr: 192.168.169.130:8848
                data-id: ${spring.application.name}
                group-id: DEFAULT_GROUP
                data-type: json
                rule-type: flow
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    
    
    • 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

    添加Nacos业务规则配置:

    {
        "resource": "/rateLimit/byUrl",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • resource:资源名称
    • limitApp:来源应用
    • grade:阈值类型,0表示线程数,1表示QPS
    • count:单机阈值
    • strategy:流控模式,0表示直接,1表示关联,2表示链路
    • controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
    • clusterMode:是否集群

    在这里插入图片描述

    重启后,访问微服务任一接口就出现原来的配置,持久化,可以说是初始化,之后可以改,一直存在nacos。

    在这里插入图片描述

  • 相关阅读:
    [2022 Google开发者大会] 机器学习-TensorFlow会议记录
    Deno 中使用 @typescript/vfs 生成 DTS 文件
    unity urp 实现遮挡显示角色轮廓
    数字图像处理-图像压缩
    Power BI 如何解决月份排序错误/乱序问题(自定义顺序/正确排序)
    使用QGIS转换矢量数据投影
    C语言:从键盘输入一个字符串,将其中的小写字母全部转换成大写字母,然后输出到一个磁盘文件“test“中保存,输入的字符以‘!‘结束
    Python常用的新模块特性
    mysql高阶语句(一)
    算法——滑动窗口(Sliding Window)
  • 原文地址:https://blog.csdn.net/qq_43409401/article/details/128005141