SpringCloud学习笔记(十四)Sentinel 实现熔断与限流

目录

一、Sentinel介绍

1、是什么

2、Hystrix与Sentinel比较

二、Sentinel下载安装运行

安装步骤

三、初始化演示工程

新建 cloudalibaba-sentinel-service8401

1)pom.xml

2)application.yml

3)主启动类

 4)controller层

 5)测试

四、流控规则

1、基本介绍

1)解释说明

2)QPS和线程数的区别

3)重要属性

2、流控模式

1)直接(默认)

2)关联

 3)链路

3、流控效果

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

2)预热

3)WarmUp配置

4)排队等待

五、降级规则 

1、熔断降级概述

2、降级策略介绍

3、RT

1)介绍

2)实操

4、异常比例

1)介绍

2)实操

 5、异常数

1)介绍

2)实战

六、热点key限流

1、基本介绍

2、实操

3、参数例外项

七、系统规则

1、各项配置参数说明 

2、@SentinelResource配置

1)按资源名称限流+后续处理

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

3)上面兜底方案面临的问题

4)客户自定义限流处理逻辑

 5)更多注解属性说明

3、服务熔断功能

1)Ribbon系列——提供者9003/9004

2)Ribbon系列——消费者84

①无配置

 ②只配置fallback

 ③ 只配置blockHandler

 ④fallback和blockHandler都配置

忽略异常

4、全局降级

 5、持久化


一、Sentinel介绍

官方Github

官方文档

1、是什么

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Sentinel 具有以下特征:

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
  • 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

Sentinel 的主要特性:

—句话解释,之前我们讲解过的Hystrix。

2、Hystrix与Sentinel比较

Hystrix:

  1. 需要我们程序员自己手工搭建监控平台
  2. 没有一套web界面可以给我们进行更加细粒度化得配置流控、速率控制、服务熔断、服务降级

Sentinel:

  1. 单独一个组件,可以独立出来。
  2. 直接界面化的细粒度统一配置。

约定 > 配置 > 编码

都可以写在代码里面,但是我们本次还是大规模的学习使用配置和注解的方式,尽量少写代码

二、Sentinel下载安装运行

官方文档

服务使用中的各种问题:

  • 服务雪崩
  • 服务降级
  • 服务熔断
  • 服务限流

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

安装步骤

1、下载

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

image-20220323143055298

2、运行命令

前提:java8环境OK + 8080端口不能被占用

运行命令:

java -jar sentinel-dashboard-1.7.1.jar

由于我的8080端口被占用,我换成了8888端口 

java -jar sentinel-dashboard-1.7.1.jar --server.port=8888

3、访问sentinel管理界面

http://localhost:8888,登录账号密码均为sentinel

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第1张图片

三、初始化演示工程

启动Nacos8848成功

新建 cloudalibaba-sentinel-service8401

1)pom.xml



    
        cloud2020
        springcloud
        1.0-SNAPSHOT
    
    4.0.0

    cloudalibaba-sentinel-service8401

    
        
        
            com.alibaba.csp
            sentinel-datasource-nacos
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-sentinel
        
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        
        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-actuator
        

        
        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            springcloud
            cloud-api-commons
            ${project.version}
        

    

2)application.yml

server:
  port: 8401
spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        # 服务注册中心 # sentinel注册进nacos
        server-addr: localhost:8848
    sentinel:
      transport:
        # 配置 Sentinel Dashboard 的地址
        dashboard: localhost:8888
        # 默认8719 ,如果端口被占用,端口号会自动 +1,直到找到未被占用的端口,提供给 sentinel 的监控端口
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'

3)主启动类

@SpringBootApplication
@EnableDiscoveryClient
public class MainApp8401 {
    public static void main(String[] args) {
        SpringApplication.run(MainApp8401.class,args);
    }
}

 4)controller层

@RestController
@Slf4j
public class FlowLimitController {
    @GetMapping("/testA")
    public String testA() {
        return "------testA";
    }

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

}

 5)测试

空空如也,啥都没有

Sentinel采用的懒加载说明:

执行一次访问即可:http://localhost:8401/testA、http://localhost:8401/testB

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第2张图片

结论:sentinel8080正在监控微服务8401

四、流控规则

1、基本介绍

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第3张图片

1)解释说明

  • 资源名:唯一名称,默认请求路径
  • 针对来源:sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
  • 阈值类型/单机值:
    • QPS(每秒钟的请求数量):当调用该api就QPS达到阈值的时候,进行限流
    • 线程数.当调用该api的线程数达到阈值的时候,进行限流
  • 是否集群:不需要集群
  • 流控模式:
    • 直接:api达到限流条件时,直接限流。分为QPS和线程数
    • 关联:当关联的资源达到阈值时,就限流自己。
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】
  • 流控效果:
    • 快速失败:直接抛异常
    • warm up:根据codeFactor(冷加载因子,默认3)的值,从阈值codeFactor,经过预热时长,才达到设置的QPS阈值
    • 排队等待:匀速排毒,让请求以匀速通过,阈值类型必须设置为QPS,否则无效

2)QPS和线程数的区别

  • QPS 类似于银行的保安:所有的请求到Sentinel 后,他会根据阈值放行,超过报错
  • 线程数类似于银行的窗口:所有的请求会被放进来,但如果阈值设置为1,那么其他的请求就会报错也就是,银行里只有一个窗口,一个人在办理业务中,其他人跑过来则会告诉你“现在不行,没到你”

在这里插入图片描述

3)重要属性

Field 说明 默认值
resource  资源名,资源名是限流规则的作用对象    
count 限流阈值    
grade  限流阈值类型,QPS 模式(1)或并发线程数模式(0)     QPS 模式
limitApp 流控针对的调用来源 default,代表不区分调用来源
strategy 调用关系限流策略:直接、链路、关联 根据资源本身(直接)
controlBehavior  流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 直接拒绝
clusterMode 是否集群限流    否

2、流控模式

1)直接(默认)

  • 直接->快速失败:系统默认

  • 配置及说明:表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第4张图片

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

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第5张图片

 思考

直接调用默认报错信息,技术方面OK,但是否应该有我们自己的后续处理?类似有个fallback的兜底方法?

2)关联

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

设置testA

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

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第6张图片

测试

访问testB成功 

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第7张图片

postman里新建多线程集合组: 

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第8张图片

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第9张图片

Run: 

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第10张图片

20个线程,每隔0.3s访问一次 

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第11张图片

 运行后发现testA挂了,点击访问:http://localhost:8401/testA

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第12张图片

 3)链路

只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【API级别的针对来源】

3、流控效果

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

直接失败,抛出异常:Blocked by Sentinel (flow limiting)

2)预热

Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考 流量控制 - Warm Up 文档,具体的例子可以参见 WarmUpFlowDemo

通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:

3)WarmUp配置

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

 系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10。效果为:开始访问 localhost:/testA 时每秒请求别超过10/3个才能正常访问,5秒后可以接受的请求可以达到每秒10次。

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第13张图片

 应用场景

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

4)排队等待

匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效

设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第14张图片

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

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

该方式的作用如下图所示:

五、降级规则 

1、熔断降级概述

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。

现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。

2、降级策略介绍

image-20220323155804020

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

  • 平均响应时间超出阈值在时间窗口内通过的请求>=5,两个条件同时满足后触发降级
  • 窗口期过后关闭断路器
  • RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

异常比列(秒级)

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

异常数(分钟级)

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

进一步说明:

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

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

Sentinel 的断路器是没有半开状态的。(Sentinei 1.8.0 已有半开状态)

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

3、RT

1)介绍

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

注意:Sentinel 1.7.0才有平均响应时间DEGRADE_GRADE_RT),Sentinel 1.8.0的没有这项,取而代之的是慢调用比例 (SLOW_REQUEST_RATIO)。

慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。熔断降级 · alibaba/Sentinel Wiki · GitHub

接下来讲解Sentinel 1.7.0的。 

image-20220323160504510

2)实操

在controller中加入textD

    @GetMapping("/testD")
    public String testD(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("testD 测试RT");
        return "------testD";
    }

 配置:

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第15张图片

jmeter压测:永远一秒钟打进来10个线程(大于5个了)调用testD

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第16张图片

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第17张图片

压测时访问 /testD 

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第18张图片

停止压测后:

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第19张图片

结论:

按照上述配置,永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了,后续停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复正常。

4、异常比例

1)介绍

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

注意,与Sentinel 1.8.0相比,有些不同(Sentinel 1.8.0才有的半开状态),Sentinel 1.8.0的如下:

异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。熔断降级 · alibaba/Sentinel Wiki · GitHub

 接下来讲解Sentinel 1.7.0的。

image-20220323161743863

2)实操

在controller中加入textE

    @GetMapping("/testE")
    public String testE(){
        log.info("testE 测试异常数");
        int a = 10/0;
        return "------testE";
    }

 配置

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第20张图片

 单独一次访问:http://localhost:8401/testE

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第21张图片

jmeter压测后:

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第22张图片

结论

按照上述配置,单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。

断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了

 5、异常数

1)介绍

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

说明:假设时间窗口期为10秒,设置的异常数不能超过5,统计阶段第1秒的时候有5个异常,服务熔断后进入时间窗口期,时间窗口期结束,但是一分钟还没结束,那么此时的异常数还是5个,又进入熔断状态。

注意,与Sentinel 1.8.0相比,有些不同(Sentinel 1.8.0才有的半开状态),Sentinel 1.8.0的如下:

异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

image-20220323162249143

接下来讲解Sentinel 1.7.0的。

异常数是按照分钟统计的,时间窗口一定要大于等于60秒

2)实战

配置:

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第23张图片

测试:

一到五次访问会报错,第六次访问后会服务降级,直到时间窗口期结束,服务才开始报错 

六、热点key限流

1、基本介绍

何为热点:热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的 Top N 数据,并对其访问进行限流或者其它操作:

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

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

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

承上启下复习

兜底方法,分为 系统默认 和 客户自定义 两种。

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

我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?

结论 - 从HystrixCommand到@SentinelResource

2、实操

在controller中加入:

    @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
    public String testHotKey(@RequestParam(value = "p1",required = false)String p1,
                             @RequestParam(value = "p2",required = false)String p2){
        return "------testHotKty";
    }
    public String deal_testHotKey(String p1, String p2, BlockException exception){
        return "------testHotKty,o(T~~T)o";
    }

 说明:

  • @SentinelResource(value = "testHotKey")异常打到了前台用户界面看到,不友好
  • @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")方法 testHotKey 里面第一个参数只要QPS超过每秒1次,马上降级处理,异常用了我们自己定义的兜底方法。

配置:

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第24张图片

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第25张图片

  • 限流模式只支持QPS模式,固定写死了。(这才叫热点)
  • @SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推,单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。

上面的抓图表示第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用deal_testHotKey支持方法。

测试: 

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第26张图片

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第27张图片

3、参数例外项

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

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

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

热点参数的注意点,参数必须是基本类型或者String

 SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第28张图片

测试:

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第29张图片

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第30张图片

说明:

  • @SentinelResource :处理的是Sentine控制台配置的违规情况,有blockHandler方法配置的兜底处理
  • @RuntimeException:int age=10/0,这个是java运行时报出的运行时异异常RunTimeException,@SentineResource不管

七、系统规则

1、各项配置参数说明 

image-20220324144124506

2、@SentinelResource配置

1)按资源名称限流+后续处理

 pom文件增加


    com.atguigu.springcloud
    cloud-api-commons
    ${project.version}

新增controller

@RestController
public class RateLimitController {
    @GetMapping("/byResource")
    @SentinelResource(value = "byResource",blockHandler = "handleException")
    public CommonResult byResource(){
        return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
    }
    public CommonResult handleException(BlockException exception){
        return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
    }

}

配置流控规则

簇点链路:byResource 是资源

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第31张图片

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第32张图片

 表示1秒钟内查询次数大于1,就跑到我们自定义的兜底方法

测试

额外问题

此时关闭服务8401看看:Sentinel控制台,流控规则消失了?????临时/持久?

 SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第33张图片

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

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

RateLimitController新增

    @GetMapping("/rateLimit/byUrl")
    public CommonResult byUrl(){
        return new CommonResult(200,"按url限流测试",new Payment(2020L,"serial002"));
    }

 簇点链路:byUrl 是资源

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第34张图片

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第35张图片

测试

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第36张图片

3)上面兜底方案面临的问题

  1. 系统默认的,没有体现我们自己的业务要求

  2. 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观

  3. 每个业务方法都添加一个兜底的,那代码膨胀加剧

  4. 全局统一的处理方法没有体现

4)客户自定义限流处理逻辑

创建 CustomerBlockHandler 类用于自定义限流处理逻辑

public class CustomerBlockHandler {
    public static CommonResult handlerException1(BlockException exception){
        return new CommonResult(4444,"按用户自定义,global handlerException.......1");
    }
    public static CommonResult handlerException2(BlockException exception){
        return new CommonResult(4444,"按用户自定义,global handlerException.......2");
    }
}

controller新增

找CustomerBlockHandler类里的handleException2方法进行兜底处理

    @GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "customerBlockHandler",
                 blockHandlerClass = CustomerBlockHandler.class,
                 blockHandler = "handlerException2")
    public CommonResult customerBlockHandler(){
        return new CommonResult(200,"按用户自定义",new Payment(2020L,"serial003"));
    }

 测试:

对资源名称流控

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第37张图片

 对URL地址流控

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第38张图片

在设置了 URL地址流控的前提下,设置按资源名称流控无效 

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第39张图片

 URL地址流控优先级大于资源名称流控

 5)更多注解属性说明

@SentinelResource注解最主要的两个用法:限流控制和熔断降级的具体使用案例介绍完了。另外,该注解还有一些其他更精细化的配置,比如忽略某些异常的配置、默认降级函数等等,具体可见如下说明:

  • value:资源名称,必需项(不能为空)
  • entryType:entry类型,可选项(默认为 EntryType.OUT)
  • 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 逻辑中,而是会原样抛出。

3、服务熔断功能

sentinel整合ribbon+openFeign+fallback

1)Ribbon系列——提供者9003/9004

启动nacos和sentinel,新建cloudalibaba-provider-payment9003/9004两个一样的做法

pom.xml



    
        cloud2020
        com.atguigu.springcloud
        1.0-SNAPSHOT
    
    4.0.0

    cloudalibaba-provider-payment9003
    
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        

        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-actuator
        

        
        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            com.atguigu.springcloud
            cloud-api-commons
            ${project.version}
        
    

 application.yml

server:
  port: 9003
spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
management:
  endpoints:
    web:
      exposure:
        include: '*'

controller层

@RestController
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    //模拟sql查询
    public static HashMap hashMap = new HashMap<>();
    static {
        hashMap.put(1L, new Payment(1L, "xcxcxcxcxcxcxcxcxcxcxcxcxc11111111"));
        hashMap.put(2L, new Payment(2L, "xcxcxcxcggggggggg2222222222222222"));
        hashMap.put(3L, new Payment(3L, "xcxcxcxccxxcxcfafdgdgdsgdsgds33333"));
    }


    @GetMapping("/payment/get/{id}")
    public CommonResult paymentSql(@PathVariable("id")Long id){
        Payment payment = hashMap.get(id);
        CommonResult result = new CommonResult(200, "from mysql, server port : " + serverPort + " ,查询成功", payment);
        return result;
    }
}

2)Ribbon系列——消费者84

新建cloudalibaba-consumer-nacos-order84

 pom.xml



    
        cloud2020
        springcloud
        1.0-SNAPSHOT
    
    4.0.0

    cloudalibaba-consumer-nacos-order84
    
        
        
            com.alibaba.csp
            sentinel-datasource-nacos
        
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-sentinel
        
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-actuator
        

        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
        
        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            com.atguigu.springcloud
            cloud-api-commons
            ${project.version}
        
    

application.yml 

由于我的8080端口被占用,我的sentinel的端口改成了8888

java -jar sentinel.jar --server.port=8888
server:
  port: 84
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8888
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719
  application:
    name: nacos-order-consumer

主启动类 

@EnableDiscoveryClient
@SpringBootApplication
public class OrderMain84 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain84.class,args);
    }
}

 配置类

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

controller层

 fallback管运行异常、blockHandler管配置违规

①无配置

@RestController
@Slf4j
public class CircleBreakerController {
    private static final String PAYMENT_URL="http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback")//没有配置
    public CommonResult fallback(@PathVariable("id")Long id){
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
        }else if(id > 4){
            throw new NullPointerException("NullPointerException...空指针异常");
        }
        return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
    }
}

测试:

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第40张图片

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第41张图片

 ②只配置fallback

@RestController
@Slf4j
public class CircleBreakerController {
    private static final String PAYMENT_URL="http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/fallback/{id}")
    //@SentinelResource(value = "fallback")//没有配置
    @SentinelResource(value = "fallback", fallback = "handleFallback") //fallback只处理业务异常
    public CommonResult fallback(@PathVariable("id")Long id){
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
        }else if(id > 4){
            throw new NullPointerException("NullPointerException...空指针异常");
        }
        return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
    }

    //兜底方法
    public CommonResult handleFallback(@PathVariable("id")Long id, Throwable e){
        Payment payment = new Payment(id, null);
        return new CommonResult(444, "---兜底异常handlerFallback,exception内容:"+e.getMessage(),payment);
    }
}

 测试:

 ③ 只配置blockHandler

@RestController
@Slf4j
public class CircleBreakerController {
    private static final String PAYMENT_URL="http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback",blockHandler = "blockHandler")
    public CommonResult fallback(@PathVariable("id")Long id){
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
        }else if(id > 4){
            throw new NullPointerException("NullPointerException...空指针异常");
        }
        return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
    }

    public CommonResult blockHandler(@PathVariable("id")Long id, BlockException e){
        Payment payment = new Payment(id, "null");
        return new CommonResult(445, "blockHandler-sentinel限流,blockException:"+e.getMessage(),payment);
    }
}

 本例sentinel需配置:

簇点链路:

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第42张图片

服务降级:

异常超过2次后,断路器打开

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第43张图片

测试 

第一次和第二次访问还是报异常:

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第44张图片

 第三次访问

 时间窗口期80s过了之后:又恢复异常

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第45张图片

 ④fallback和blockHandler都配置

@RestController
@Slf4j
public class CircleBreakerController {
    private static final String PAYMENT_URL="http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback",blockHandler = "blockHandler",fallback = "handleFallback")
    public CommonResult fallback(@PathVariable("id")Long id){
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
        }else if(id > 4){
            throw new NullPointerException("NullPointerException...空指针异常");
        }
        return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
    }

    //兜底方法
    public CommonResult handleFallback(@PathVariable("id")Long id, Throwable e){
        Payment payment = new Payment(id, null);
        return new CommonResult(444, "---兜底异常handlerFallback,exception内容:"+e.getMessage(),payment);
    }

    public CommonResult blockHandler(@PathVariable("id")Long id, BlockException e){
        Payment payment = new Payment(id, "null");
        return new CommonResult(445, "blockHandler-sentinel限流,blockException:"+e.getMessage(),payment);
    }
}

 本例sentinel需配置:

簇点链路:

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第46张图片

服务降级:

异常超过2次后,断路器打开

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第47张图片

 测试 

第一次和第二次访问:

 第三次访问

结论:

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

忽略异常

@RestController
@Slf4j
public class CircleBreakerController {
    private static final String PAYMENT_URL="http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback",blockHandler = "blockHandler",fallback = "handleFallback",
            exceptionsToIgnore = {IllegalArgumentException.class})
    //exceptionsToIgnore有这个异常不走兜底方法
    public CommonResult fallback(@PathVariable("id")Long id){
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
        }else if(id > 4){
            throw new NullPointerException("NullPointerException...空指针异常");
        }
        return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
    }

    //兜底方法
    public CommonResult handleFallback(@PathVariable("id")Long id, Throwable e){
        Payment payment = new Payment(id, null);
        return new CommonResult(444, "---兜底异常handlerFallback,exception内容:"+e.getMessage(),payment);
    }

    public CommonResult blockHandler(@PathVariable("id")Long id, BlockException e){
        Payment payment = new Payment(id, "null");
        return new CommonResult(445, "blockHandler-sentinel限流,blockException:"+e.getMessage(),payment);
    }
}

测试:

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第48张图片

4、全局降级

上面是单个进行 fallback 和 blockhandler 的测试,下面是整合 openfeign 实现把降级方法解耦。和Hystrix 几乎一摸一样!

添加open-feign依赖:


    org.springframework.cloud
    spring-cloud-starter-openfeign

yml 追加如下配置

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

 主启动类添加注解 : @EnableFeignClients 激活open-feign

service:

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult paymentSql(@PathVariable("id")Long id);
    //方法名也要和provider-payment9003/9004里的一样
}
@Component
public class PaymentFallbackService implements PaymentService{
    @Override
    public CommonResult paymentSql(Long id) {
        return new CommonResult(44444,"服务降级返回,---PaymentFallbackService");
    }
}

controllert添加:

    @Resource
    private PaymentService paymentService;

    @GetMapping(value = "/consumer/paymentSQL/{id}")
    public CommonResult paymentSQL(@PathVariable Long id) {
        return paymentService.paymentSql(id);
    }

 测试:

为了模拟异常,在远程调用方添加延时(open-feign默认延时大于1秒才会回调),如下。

测试时发现如果只给一个微服务加延时,另一个不加,会一直调用没有延时的微服务,所以要么两个都加延时或者停掉一个。

    @GetMapping("/payment/get/{id}")
    public CommonResult paymentSql(@PathVariable("id")Long id) throws InterruptedException {
        Payment payment = hashMap.get(id);
        CommonResult result = new CommonResult(200, "from mysql, server port : " + serverPort + " ,查询成功", payment);
        TimeUnit.SECONDS.sleep(6);
        return result;
    }

SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第49张图片

 5、持久化

目前的sentinel 当重启以后,数据都会丢失,和 nacos 类似原理。需要持久化。它可以被持久化到 nacos 的数据库中。

pom文件


    com.alibaba.csp
    sentinel-datasource-nacos

 application.yml

spring:
  cloud:
    sentinel:
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}
            group: DEFAULT_GROUP
            data-type: json
            rule-type: flow

去nacos上创建一个dataid ,名字和yml配置的一致,json格式,内容如下: 

 SpringCloud学习笔记(十四)Sentinel 实现熔断与限流_第50张图片

[
    {
        "resource": "fallback",
        "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表示排队等待;
  • cIusterM0de是否集群

启动应用,发现存在 关于资源 "fallback" 的流控规则.

总结: 就是在 sentinel 启动的时候,去 nacos 上读取相关规则配置信息,实际上它规则的持久化,就是第三步,粘贴到nacos上保存下来,就算以后在 sentinel 上面修改了,重启应用以后也是无效。

你可能感兴趣的:(#,SpringCloud,java,spring,spring,cloud,sentinel,微服务)