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 是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护等多个维度来帮助您保障微服务的稳定性。

SpringCloud微服务(十一)——Sentinel服务熔断限流_第1张图片

流量控制

流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第2张图片

演示工程

依赖配置

<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>

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: "*"

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

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

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第3张图片

流控规则

流量控制

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

官网: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   -----";
    }
直接(默认)

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

阈值类型:QPS或线程数

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

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第4张图片

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

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

在这里插入图片描述

关联

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

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

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

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第5张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第6张图片
SpringCloud微服务(十一)——Sentinel服务熔断限流_第7张图片

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

在这里插入图片描述

链路

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

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

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

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

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

     	          machine-root
                    /       \
                   /         \
             Entrance1     Entrance2
                /             \
               /               \
      DefaultNode(nodeA)   DefaultNode(nodeA)

上图中来自入口 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/

SpringCloud微服务(十一)——Sentinel服务熔断限流_第8张图片

系统初始化的阈值为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毫秒。

SpringCloud微服务(十一)——Sentinel服务熔断限流_第9张图片

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

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

postman模拟并发密集访问testA

controller打印日志

    @GetMapping("/testA")
    public String testA(){
        log.info(Thread.currentThread().getName() + "...testA ");
        return "testA   -----";
    }

每秒一个:

SpringCloud微服务(十一)——Sentinel服务熔断限流_第10张图片

降级规则

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

官网: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 来配置。

SpringCloud微服务(十一)——Sentinel服务熔断限流_第11张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第12张图片

使用jmeter压力测试

SpringCloud微服务(十一)——Sentinel服务熔断限流_第13张图片
SpringCloud微服务(十一)——Sentinel服务熔断限流_第14张图片

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

异常比例

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第15张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第16张图片

条件和设置:

SpringCloud微服务(十一)——Sentinel服务熔断限流_第17张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第18张图片

异常数

异常数是按分钟统计的

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第19张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第20张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第21张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第22张图片

热点规则

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

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

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

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

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第23张图片

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---------";
    }

sentinel上设置热点key:

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第24张图片

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

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第25张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第26张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第27张图片

不是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参数类型

SpringCloud微服务(十一)——Sentinel服务熔断限流_第28张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第29张图片

系统规则

说白了就是这个系统规则会对所有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 达到阈值即触发系统保护。

SpringCloud微服务(十一)——Sentinel服务熔断限流_第30张图片

比如选择入口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服务不可用" );
    }
}

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第31张图片

按照Url地址限流+后续处理

    @GetMapping("/rateLimit/byUrl")
    @SentinelResource(value = "byUrl")
    public CommonResult byUrl(){
        return new CommonResult(200, "by url限流测试OK", new Payment(2020L, IdUtil.simpleUUID()));
    }

通过访问的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");
    }
}

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()));
    }

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

超过流控阈值:

SpringCloud微服务(十一)——Sentinel服务熔断限流_第32张图片

注意点

注意:注解方式埋点不支持 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>

server:
  port: 9003/9004

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

management:
  endpoints:
    web:
      exposure:
        include: "*"

启动类:@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);
    }
}

消费者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>

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

启动类:@EnableDiscoveryClient

Ribbon系列

负载均衡

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

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

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

无配置

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;
    }

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

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第33张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第34张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第35张图片

只配置程序异常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);
    }

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

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第36张图片

只配置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);
    }

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

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第37张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第38张图片

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);
    }

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

配置了blockHandler和fallback

设置流控规则:

SpringCloud微服务(十一)——Sentinel服务熔断限流_第39张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第40张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第41张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第42张图片

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

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

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

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

Feign系列

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

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-openfeignartifactId>
dependency>

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

接口跟服务提供者的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);
}

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");
    }
    
}

服务消费者:

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

启动类加@EnableFeignClients

yml配置加:

#激活sentinel对feign的支持
feign:  
  sentinel:    
    enabled: true

contoller调用:

    @Resource
    private PaymentFeign paymentFeign;

    // 直接调用feign
    @GetMapping("/consumer/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id{
        return paymentFeign.paymentSQL(id);
    }

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

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

框架比较:

Sentinel Hystrix resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口(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>

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: "*"

添加Nacos业务规则配置:

{
    "resource": "/rateLimit/byUrl",
    "limitApp": "default",
    "grade": 1,
    "count": 1,
    "strategy": 0,
    "controlBehavior": 0,
    "clusterMode": false
}

  • resource:资源名称
  • limitApp:来源应用
  • grade:阈值类型,0表示线程数,1表示QPS
  • count:单机阈值
  • strategy:流控模式,0表示直接,1表示关联,2表示链路
  • controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
  • clusterMode:是否集群

SpringCloud微服务(十一)——Sentinel服务熔断限流_第43张图片

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

SpringCloud微服务(十一)——Sentinel服务熔断限流_第44张图片

你可能感兴趣的:(java,java后端,sentinel,sentinel,微服务)