SpringCloud Alibaba-Sentinel 实现熔断与限流

一、高并发带来的问题

1.1 服务雪崩

在分布式系统中,由于网络原因或自身的原因,服务一般无法保证 100% 可用。如果一个服务出现了问题,调用这个服务救护出现线程阻塞的情况,此时若有大量的请求涌入,就会出现多条线程阻塞等待,进而导致服务瘫痪。

由于服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的”雪崩效应“。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第1张图片
雪崩发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。我们无法完全杜绝雪崩源头的发生,只有做好足够的容错,保证在一个服务发生问题,不会影响到其他服务的正常运行。

1.2 常见容错方案

常见的容错方案有隔离、超时、限流、熔断、降级这几种。

隔离

它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其他模块,不影响整体的系统服务。常见的隔离方式有:线程池隔离和信号量隔离。

超时

在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应,就断开请求,释放掉线程。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第2张图片

限流

限流就是限制系统的输入和输出流量,以达到保护系统的目的。为了保证系统的稳固运行,一旦达到需要限制的阈值,就需要限制流量,并采取少量措施以完成限制流量的目的。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第3张图片

熔断

在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第4张图片
服务熔断一般有三种状态:

  • 熔断关闭状态(Closed)
    服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。

  • 熔断开启状态(Open)
    后续对该服务接口的调用不再经过网络,直接执行本地的 fallback 方法

  • 半熔断状态(Half-Open)
    尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。

降级

降级其实就是为服务提供一个托底方案,一旦服务无法正常调用,就使用托底方案。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第5张图片

1.3 常见的容错组件

  • Hystrix
    Hystrix 是由 Netflix 开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。

  • Resilience4J
    Resilience4J 一款非常轻量、简单,并且文档非常清晰、丰富的熔断工具,这也是 Hystrix 官方推荐的替代产品。不仅如此,Resilience4J 还支持原生 SpringBoot 1.x/2.x,而且监控也支持和 prometheus 等多款主流产品进行整合。

  • Sentinel
    Sentinel 是阿里巴巴开源的一款断路器实现,本身在阿里内部已经被大规模采用,非常稳定。

下面是三大组件在各方面的对比:

Sentinel Hystrix Resilience4J
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于 RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
限流 基于 QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
流量整形 支持预热模式、匀速器模式、预热排队模式 不支持 简单的 Rate Limiter 模式
系统自适应保护 支持 不支持 不支持
控制台 提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 简单的监控查看 不提供控制台,可对接其他监控系统

二、Sentinel

2.1 什么是 Sentinel

Sentinel(分布式系统的流量防卫兵)是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。

Sentinel 具有以下特性:

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

Sentinel 分为两个部分:

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

Sentinel 中文官网

Sentinel 下载地址

Sentinel 主要特性:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第6张图片

2.2 安装 Sentinel

1、官网下载最新版的 Sentinel
SpringCloud Alibaba-Sentinel 实现熔断与限流_第7张图片
2、解压后,在 jar 包所在目录下,打开终端,执行 java -jar sentinel-dashboard-1.8.5.jar 命令,将 Sentinel 启动。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第8张图片
3、启动成功后,访问:http://localhost:8080,登录账号密码均为sentinel
SpringCloud Alibaba-Sentinel 实现熔断与限流_第9张图片
注意:8080端口不能被占用,需 JDK1.8 及以上。

2.3 案例

2.3.1 新建 cloudablibaba-sentinel-service8401 子模块

2.3.2 pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020artifactId>
        <groupId>com.atguigu.springcloudgroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>cloudalibaba-sentinel-service8401artifactId>

    <properties>
        <maven.compiler.source>8maven.compiler.source>
        <maven.compiler.target>8maven.compiler.target>
    properties>

    <dependencies>
        <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>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
        dependency>
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>5.7.17version>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
    dependencies>
project>

2.3.3 application.yml

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848   #Nacos 服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080    # 配置 Sentinel dashboard 地址
        port: 8719 # 默认 8719 端口,假如被占用会自动从 8719 开始依次 +1 扫描 , 直至找到未被占用的端口

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

2.3.4 主启动类

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

2.3.5 业务类

@RestController
public class FlowLimitController {

    @GetMapping("/testA")
    public String testA(){
        return "--------testA";
    }

    @GetMapping("/testB")
    public String testB(){
        return "--------testB";
    }
}

2.3.6 测试

先启动 Nacos 服务,然后启动 Sentinel8080,最后启动微服务 service8401。

服务启动成功后,访问 Sentinel,会发现Sentinel 控制台上什么都没有,这是因为 Sentinel 采用了懒加载模式,我们只需执行一次访问即可。
在这里插入图片描述
多次访问 http://localhost:8401/testB
SpringCloud Alibaba-Sentinel 实现熔断与限流_第10张图片
可以看出 sentinel8080 正在监控微服务8401

2.4、簇点链路

当请求进入微服务时,首先会访问 DispatcherServlet,然后进入 Controller、Service、Mapper,这样的一个调用链就叫做簇点链路。簇点链路中被监控的每一个接口就是一个资源。

默认情况下,Sentinel 会监控 SpringMVC 的每一个断点(Endpoint,也就是 Controller 的方法),因此 SpringMVC 的每一个断点(Endpoint)就是调用链路中的一个资源。

例如,刚才访问的 cloudalibaba-sentinel-service 中的 FlowLimitController 中的断点:/testB
SpringCloud Alibaba-Sentinel 实现熔断与限流_第11张图片

流控、熔断等都是针对簇点链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则:

  • 流控:流量控制
  • 降级:降级熔断
  • 热点:热电参数限流,是限流的一种
  • 授权:请求的权限控制

2.5 流控规则

2.5.1 基本介绍


资源名:唯一名称,默认请求路径。
针对来源:Sentinel 可以针对调用者进行限流,通过填写微服务名来指定,默认 default,意思是不区分来源,全部限制
阈值类型/单击阈值:

  • QPS (每秒请求数量):当调用该接口的 QPS 达到阈值的时候,进行限流
  • 线程数:当调用该接口的线程数达到阈值的时候,进行限流

是否集群:暂不需要集群
流控模式:

  • 直接:接口达到限流条件时,直接限流
  • 关联:当关联的资源达到阈值时,就限流自己
  • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【接口级别的针对来源】

流控效果:

  • 快速失败:当请求超过QPS阈值时,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。
  • Warm Up:warm up也叫预热模式,是应对服务冷启动的一种方案。请求阈值初始值是 maxThreshold / coldFactor,持续指定时长后,逐渐提高到maxThreshold值。而coldFactor的默认值是3。
  • 排队等待:让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃

2.5.2 流控模式

2.5.2.1 直接

直接模式:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式

QPS

QPS:每秒查询率(QPS,Queries-per-second)是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。

在流控规则下,点击新增流控规则,在弹出的表单中填写限流规则,如下:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第12张图片
该流控规则表示 1 秒钟内查询次数若超过 1 次,就会直接快速失败,报默认错误。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第13张图片

并发线程数

线程数:当调用该接口的线程数达到阈值的时候,进行限流

在流控规则下,点击新增流控规则,在弹出的表单中填写限流规则,如下:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第14张图片
为了模拟出限流效果,需要修改下 testA() 方法,修改如下:

@GetMapping("/testA")
public String testA(){
    try {
        Thread.sleep(800);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "--------testA";
}

在浏览器多次快速访问 http://localhost:8401/testA
SpringCloud Alibaba-Sentinel 实现熔断与限流_第15张图片

后续所有的演示案例均以 QPS 为例。

2.5.2.2 关联

关联模式:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流。即当与 A 关联的资源 B 的访问请求达到阈值后,就会限流 A。

配置规则
SpringCloud Alibaba-Sentinel 实现熔断与限流_第16张图片
语法说明:当 /testB 资源访问量触发阈值时,就会对 /testA 资源限流,避免影响 /testB 资源。

使用场景:比如用户支付时需要修改订单状态,同时用户要查询订单。查询和修改操作会争抢数据库锁,产生竞争。业务需求是优先支付和更新订单的业务,因此当修改订单业务触发阈值时,需要对查询订单业务限流。

测试
打开 postman,新建一个 collection,在该 collection 中创建两个一样的 Request,Request 的具体访问路径如下:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第17张图片
测试 Request 通过。

接着使用 postman 模拟并发密集访问 testB。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第18张图片
点击“Run Sentinel”,接着返回浏览器访问 http://localhost:8401/testA
SpringCloud Alibaba-Sentinel 实现熔断与限流_第19张图片
大批量线程高并发访问 B,导致 A 失效。

2.5.2.3 链路

链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。

配置示例

例如有两条请求链路:

  • /testA --> /testC

  • /testB --> /testC

如果只希望统计从 /testA 进入到 /testC 的请求,则可以这样配置:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第20张图片
实现

(1)新增 FlowLimitService,创建 testC() 方法。

默认情况下,FlowLimitService中的方法是不被 Sentinel 监控的,需要我们自己通过注解来标记要监控的方法。
给 FlowLimitService 的 testC 方法添加@SentinelResource注解:

@Service
public class FlowLimitService {

    @SentinelResource("testC")
    public void testC(){
        System.out.println("测试------------");
    }
}

(2)修改 FlowLimitController,让 testA 及 testB 分别调用 testC 方法。

@RestController
public class FlowLimitController {

    @Autowired
    private FlowLimitService flowLimitService;

    @GetMapping("/testA")
    public String testA(){
        flowLimitService.testC();
        return "--------testA";
    }

    @GetMapping("/testB")
    public String testB(){
        flowLimitService.testC();
        return "--------testB";
    }
}

(3)修改 application.yml
链路模式中,是对不同来源的两个链路做监控。但是 sentinel 默认会给进入 SpringMVC 的所有请求设置同一个root资源,会导致链路模式失效。

我们需要关闭这种对SpringMVC的资源聚合,修改order-service服务的application.yml文件:

spring:
  cloud:
    sentinel:
      web-context-unify: false # 关闭context整合

注意:这种关闭 context 整合的方式需要在 spring-cloud-alibaba-dependencies 2.2.0.RELEASE 版本之后。

重启服务,访问 /testA 和 /testB,可以查看到 sentinel 的簇点链路规则中,出现了新的资源:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第21张图片
(4)添加流控规则
SpringCloud Alibaba-Sentinel 实现熔断与限流_第22张图片
(5)测试
不断地快速访问 http://localhost:8401/testA 和 http://localhost:8401/testB。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第23张图片
SpringCloud Alibaba-Sentinel 实现熔断与限流_第24张图片
testA 会报错,而 testB 则不受影响。

2.5.3 流控效果

2.5.3.1 预热模式

warm up也叫预热模式,是应对服务冷启动的一种方案。请求阈值初始值是 maxThreshold / coldFactor,持续指定时长后,逐渐提高到maxThreshold值。而coldFactor的默认值是3。

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

例如,设置QPS的maxThreshold为10,预热时间为5秒,那么初始阈值就是 10 / 3 ,也就是3,然后在5秒后逐渐增长到10。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第25张图片
案例

需求:给 /testB 这个资源设置限流,最大QPS为10,利用 warm up 效果,预热时长为5秒。

流控规则如下:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第26张图片

使用 Jmeter 进行测试:

线程组配置:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第27张图片
HTTP请求配置:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第28张图片
刚刚启动时,大部分请求失败,成功的只有3个,说明QPS被限定在3:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第29张图片
随着时间推移,成功比例越来越高:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第30张图片
到Sentinel控制台查看实时监控:

2.5.3.2 排队等待

当请求超过 QPS 阈值时,快速失败和 warm up 会拒绝新的请求并抛出异常,而排队等待则是让所有请求进入到一个队列中,然后按照阈值允许的时间间隔依次执行。后来的请求必须等待前面执行完成,如果请求预期的等待时间超出最大时长,则会被拒绝。

例如:QPS = 5,即每秒请求数量为 5 ,意味着每 200ms 处理一个队列中的请求;timeout = 2000,意味着预期等待超过 2000ms 的请求会被拒绝并抛出异常。

那什么叫做预期等待时长呢?
比如现在一下子来了12 个请求,因为每200ms执行一个请求,那么:

  • 第6个请求的预期等待时长 = 200 * (6 - 1) = 1000ms
  • 第12个请求的预期等待时长 = 200 * (12-1) = 2200ms

现在,第1秒同时接收到10个请求,但第2秒只有1个请求,此时QPS的曲线这样的:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第31张图片
如果使用队列模式做流控,所有进入的请求都要排队,以固定的200ms的间隔执行,QPS会变的很平滑:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第32张图片
平滑的QPS曲线,对于服务器来说是更友好的。

案例
1)需求:给 /testB 这个资源设置限流,最大QPS为10,利用排队的流控效果,超时时长设置为5s。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第33张图片
2)Jmeter测试
SpringCloud Alibaba-Sentinel 实现熔断与限流_第34张图片
QPS为15,已经超过了我们设定的10。
如果是之前的 快速失败、warmup模式,超出的请求应该会直接报错。
但是我们看看队列模式的运行结果:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第35张图片
全部都通过了。
当队列满了以后,才会有部分请求失败:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第36张图片

2.4 降级规则

2.4.1 简介

降级规则就是设置当满足什么条件的时候,对服务进行降级。Sentinel提供了三个衡量条件:
在这里插入图片描述

慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。

解读:RT超过500ms的调用是慢调用,统计最近10000ms内的请求,如果请求量超过10次,并且慢调用比例不低于0.5,则触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。

异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。异常比率的阈值范围是 [0.0,1.0]。
一个异常比例的设置:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第37张图片
解读:统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于0.4,则触发熔断。

一个异常数的设置:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第38张图片
解读:统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于2次,则触发熔断。

2.4.2 慢调用比例

需求:给 testD 接口设置降级规则,慢调用的RT阈值为50ms,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5

(1)在 FlowLimitController 中添加 testD 方法:

@GetMapping("/testD/{id}")
public String testD(@PathVariable("id") Integer id){
    if(id == 1) {
        try {
            Thread.sleep(60);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    return "----------testD";
}

(2)浏览器访问 http://localhost:8401/testD/2
SpringCloud Alibaba-Sentinel 实现熔断与限流_第39张图片

(3)进入 Sentinel 控制台,为 testD 方法增加降级规则
SpringCloud Alibaba-Sentinel 实现熔断与限流_第40张图片

点击上图中的熔断按钮
SpringCloud Alibaba-Sentinel 实现熔断与限流_第41张图片
超过50ms的请求都会被认为是慢请求

(4)测试
在浏览器访问 http://localhost:8401/testD/1,快速刷新5次,可以发现:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第42张图片
触发了熔断,快速失败了,并且走降级逻辑。

再访问 http://localhost:8401/testD/2 ,发现也被熔断了
SpringCloud Alibaba-Sentinel 实现熔断与限流_第43张图片

2.4.3 异常比例或异常数

异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。

需求:给 testD 接口设置降级规则,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5s

(1)配置规则如下:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第44张图片
(2)修改 testD 方法逻辑,增加对 id=2 的判断

@GetMapping("/testD/{id}")
public String testD(@PathVariable("id") Integer id){
    if(id == 1) {
        try {
            Thread.sleep(60);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    } else if(id == 2){
        throw new RuntimeException("故意抛出异常,触发异常比例熔断");
    }
    return "----------testD";
}

(3)测试
访问 http://localhost:8401/testD/2
SpringCloud Alibaba-Sentinel 实现熔断与限流_第45张图片
接着,在浏览器快速访问:http://localhost:8088/testD/2,快速刷新5次,触发熔断:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第46张图片
此时,我们去访问本来应该正常的 http://localhost:8401/testD/3
SpringCloud Alibaba-Sentinel 实现熔断与限流_第47张图片
异常比例和异常数都是按照统计异常数来触发熔断的,只不过一个是统计单位时长内异常调用的比例,一个是统计单位时长内异常调用的次数。

2.5 热点 key 限流

2.5.1 简介

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

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
    热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

2.5.2 案例

(1)在 FlowLimitController 类中,增加方法 testHotKey

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "dealHandlerTestHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                         @RequestParam(value = "p2", required = false) String p2){
    return "-----------testHotKey";
}

public String dealHandlerTestHotKey(String p1, String p2, BlockException exception){
    return "-----------dealHandlerTestHotKey";
}

注解 @SentinelResource 类似于 Hystrix 中的 @HystrixCommand,可以实现自定义降级方法。在之前的案例中,当限流出问题后,都是用sentinel系统默认的提示: Blocked by Sentinel (flow limiting),通过 @SentinelResource 来实现自定义降级方法。

添加完成后,在浏览器中访问 http://localhost:8401/testHotKeySpringCloud Alibaba-Sentinel 实现熔断与限流_第48张图片
(2)配置
进入 Sentinel 控制台,点击簇点链路,点击 testHotKey 一栏的“热点”按钮
SpringCloud Alibaba-Sentinel 实现熔断与限流_第49张图片
在弹窗中填入如下规则:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第50张图片
@SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推
单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。

上图代表的含义是:对 testHotKey 这个资源的0号参数(第一个参数)做统计,每1秒相同参数的请求数不能超过 1。

(3)测试
多次快速访问 http://localhost:8401/testHotKey?p1=abc,要求访问次数超过 1
SpringCloud Alibaba-Sentinel 实现熔断与限流_第51张图片
多次快速访问 http://localhost:8401/testHotKey?p1=abc&p2=123,要求访问次数超过 1
SpringCloud Alibaba-Sentinel 实现熔断与限流_第52张图片
多次快速访问 http://localhost:8401/testHotKey?p2=123
SpringCloud Alibaba-Sentinel 实现熔断与限流_第53张图片
结论:只要访问路径中携带有参数 p1,且每秒请求次数超过 1次,就会导致服务降级。

2.5.3 参数例外项

上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流。我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样,假如当p1的值等于5时,它的阈值可以达到200。

而在实际开发中,可能部分商品是热点商品,例如秒杀商品,我们希望这部分商品的QPS限制与其它商品不一样,高一些。那就需要配置热点参数限流的高级选项了。

需求:当参数 p1 的值等于5的时候,阈值变为200,当p1不等于5的时候,阈值就是平常的1。

参数例外项设置:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第54张图片
之后点击右侧“添加”按钮,将参数例外项的设置内容添加到下方列表中。

然后点击“保存”按钮,将设置保存。

测试:

多次快速访问 http://localhost:8401/testHotKey?p1=5,可以正常返回信息。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第55张图片
多次快速访问 http://localhost:8401/testHotKey?p1=3,返回降级信息。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第56张图片
注意:热点参数必须是基本类型或者String

2.5.4 其他注意事项

修改 testHotKey 方法,让 testHotKey 方法主动抛出异常:

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "dealHandlerTestHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                         @RequestParam(value = "p2", required = false) String p2){
    int age = 10/0;
    return "-----------testHotKey";
}

public String dealHandlerTestHotKey(String p1, String p2, BlockException exception){
    return "-----------dealHandlerTestHotKey";
}

访问 http://localhost:8401/testHotKey?p1=3 进行测试
SpringCloud Alibaba-Sentinel 实现熔断与限流_第57张图片

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

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

总结 :@SentinelResource主管配置出错,运行出错该走异常走异常

2.6 系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

系统规则支持以下的模式:

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

SpringCloud Alibaba-Sentinel 实现熔断与限流_第58张图片

2.7 @SentinelResource 配置

2.7.1 按资源名称限流

修改子模块 cloudalibaba-sentinel-service8401
(1)修改pom.xml,引入自己定义的 api 通用包

<dependency>
    <groupId>com.atguigu.springcloudgroupId>
    <artifactId>cloud-api-commonsartifactId>
    <version>1.0-SNAPSHOTversion>
dependency>

(2)新建 RateLimitController

@RestController
public class RateLimitController {

    @GetMapping("/byResource")
    @SentinelResource(value = "byResource", blockHandler = "handleException")
    public CommonResult byResource(){
        return new CommonResult( 200 , " 按资源名称限流测试 OK" , new Payment( 2022L , "serial001" ));
    }

    public CommonResult handleException(BlockException blockException){
        return new CommonResult(444, blockException.getClass().getCanonicalName() + "  服务不可用");
    }
}

(3)配置限流规则
先访问 http://localhost:8401/byResource,正常
在这里插入图片描述
点击 byResource 一栏的“流控”按钮

在弹出窗中填入下面的流控规则,然后点击“新增”按钮。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第59张图片
接着多次快速访问 http://localhost:8401/byResource,使得 QPS 大于 1,触发流控规则。
在这里插入图片描述
返回的是自定义的限流信息。

2.7.2 按照Url地址限流

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息。
(1)修改 RateLimitController,增加 byUrl 方法

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

(2)测试访问 http://localhost:8401/rateLimit/byUrl,正常
SpringCloud Alibaba-Sentinel 实现熔断与限流_第60张图片
(3)Sentinel控制台配置
SpringCloud Alibaba-Sentinel 实现熔断与限流_第61张图片
SpringCloud Alibaba-Sentinel 实现熔断与限流_第62张图片
(4)测试

接着多次快速访问 http://localhost:8401/rateLimit/byUrl,使得 QPS 大于 1,触发流控规则。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第63张图片
返回的是 Sentinel 自带的限流信息。

2.7.3 上述两种方案存在的问题

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

2.7.4 自定义限流处理逻辑

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

public class CustomerBlockHandler {
    public static CommonResult handleException(BlockException blockException){
        return new CommonResult( 2022 , " 自定义的限流处理信息 ......CustomerBlockHandler.......handleException" );
    }

    public static CommonResult handleException2(BlockException blockException){
        return new CommonResult( 2022 , " 自定义的限流处理信息 ......CustomerBlockHandler.......handleException2" );
    }
}

(2) RateLimitController

/**
 *  自定义通用的限流处理逻辑,
 *  blockHandlerClass = CustomerBlockHandler.class
 *  blockHandler = handleException2
 *  上述配置:找 CustomerBlockHandler 类里的 handleException2 方法进行兜底处理
 */
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
                 blockHandlerClass = CustomerBlockHandler.class,
                 blockHandler = "handleException2")
public CommonResult customerBlockHandler(){
    return new CommonResult(200, " 按客户自定义限流处理逻辑 " );
}

(3)启动服务,浏览器访问 http://localhost:8401/rateLimit/customerBlockHandler,正常
SpringCloud Alibaba-Sentinel 实现熔断与限流_第64张图片
(4)配置限流规则
SpringCloud Alibaba-Sentinel 实现熔断与限流_第65张图片
SpringCloud Alibaba-Sentinel 实现熔断与限流_第66张图片
(5)测试

在浏览器中多次快速访问 http://localhost:8401/rateLimit/customerBlockHandler,使得 QPS 超过 1。
在这里插入图片描述
返回自定义的限流处理信息。

(6)进一步说明

2.8 服务熔断功能

2.8.1 Sentinel 整合 Ribbon

2.8.1.1 服务提供者

新建cloudalibaba-provider-payment9003/9004
(1)pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020artifactId>
        <groupId>com.atguigu.springcloudgroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>cloudalibaba-provider-payment9003artifactId>

    <properties>
        <maven.compiler.source>8maven.compiler.source>
        <maven.compiler.target>8maven.compiler.target>
    properties>

    <dependencies>
        <dependency>
            <groupId>com.atguigu.springcloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
        <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>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
        dependency>
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>5.7.17version>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
        <dependency>
            <groupId>com.atguigu.springcloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>1.0-SNAPSHOTversion>
            <scope>compilescope>
        dependency>
    dependencies>
project>

(2)application.yml
注意在创建 9004 服务时需要将端口号修改成 9004

server:
  port: 9003

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

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

(3)主启动类

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

(4)业务类

@RestController
public class PaymentController {

    @Value("${server.port}")
    private String serverPort;

    public static HashMap<Long, Payment> hashMap = new HashMap<>();

    static{
        hashMap.put(1L, new Payment(1L, "28a8c1e3bc2742d8848569891fb42181"));
        hashMap.put(2L, new Payment(2L, "bba8c1e3bc2742d8848569891ac32182"));
        hashMap.put(3L, new Payment(3L, "6ua8c1e3bc2742d8848569891xt92183"));
    }

    @GetMapping("/paymentSql/{id}")
    public CommonResult<Payment> paymentSql(@PathVariable("id") Long id){
        Payment payment = hashMap.get(id);
        CommonResult<Payment> result = new CommonResult<>(200, "from mysql, serverPort:" + serverPort, payment);
        return result;
    }
}

(5)启动 9003 和 9004
启动前,需保证 nacos 以及 sentinel 均已启动。

测试访问 http://localhost:9003/paymentSql/1
在这里插入图片描述

2.8.1.2 服务消费者

新建cloudalibaba-consumer-nacos-order84
(1)pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020artifactId>
        <groupId>com.atguigu.springcloudgroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>cloudalibaba-consumer-nacos-order84artifactId>

    <properties>
        <maven.compiler.source>8maven.compiler.source>
        <maven.compiler.target>8maven.compiler.target>
    properties>

    <dependencies>
        <dependency>
            <groupId>com.atguigu.springcloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
        <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>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
        dependency>
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>5.7.17version>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
        <dependency>
            <groupId>com.atguigu.springcloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>1.0-SNAPSHOTversion>
            <scope>compilescope>
        dependency>
    dependencies>
project>

(2)application.yml

server:
  port: 84

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719

# 消费者将要去访问的微服务名称(注册成功进 nacos 的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider

(3)主启动类

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

(4)配置类

@Component
public class ApplicationContextConfig {

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

(5)业务类

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

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback")
    public CommonResult<Payment> fallback(@PathVariable("id") Long id){
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSql/" + id, CommonResult.class, id);
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常......");
        } else if(result.getData() == null){
            throw new NullPointerException("NullPointerException,该 ID 没有对应记录,空指针异常");
        }
        return result;
    }
}

(6)测试
访问 http://localhost:84/consumer/fallback/1
在这里插入图片描述
再访问 http://localhost:84/consumer/fallback/4
SpringCloud Alibaba-Sentinel 实现熔断与限流_第67张图片
直接将错误信息返回给客户,不够友好。

2.8.1.3 只配置 fallback

修改业务类如下,在注解 @SentinelResource 中添加 fallback 属性

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

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback", fallback = "handlerFallback")
    public CommonResult<Payment> fallback(@PathVariable("id") Long id){
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSql/" + id, CommonResult.class, id);
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常......");
        } else if(result.getData() == null){
            throw new NullPointerException("NullPointerException,该 ID 没有对应记录,空指针异常");
        }
        return result;
    }

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

重启服务测试

访问 http://localhost:84/consumer/fallback/1,正常
在这里插入图片描述

访问 http://localhost:84/consumer/fallback/4,触发熔断
在这里插入图片描述
访问 http://localhost:84/consumer/fallback/5,触发熔断
在这里插入图片描述

2.8.1.4 只配置blockHandler

(1)修改业务类 CircleBreakerController,如下:

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

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback", blockHandler = "blockHandler")
    public CommonResult<Payment> fallback(@PathVariable("id") Long id){
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSql/" + id, CommonResult.class, id);
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常......");
        } else if(result.getData() == null){
            throw new NullPointerException("NullPointerException,该 ID 没有对应记录,空指针异常");
        }
        return result;
    }

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

}

(2)添加熔断规则
SpringCloud Alibaba-Sentinel 实现熔断与限流_第68张图片

SpringCloud Alibaba-Sentinel 实现熔断与限流_第69张图片
(3)测试
访问 http://localhost:84/consumer/fallback/5
SpringCloud Alibaba-Sentinel 实现熔断与限流_第70张图片
页面会报空指针异常,接着快速访问该地址,使异常数超过 2 次,触发降级。
在这里插入图片描述
页面会展示 blockHandler 方法中的返回信息。

2.8.1.4 fallback和blockHandler都配置

(1)修改业务类 CircleBreakerController,如下:

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

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")
    public CommonResult<Payment> fallback(@PathVariable("id") Long id){
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSql/" + id, CommonResult.class, id);
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常......");
        } else if(result.getData() == null){
            throw new NullPointerException("NullPointerException,该 ID 没有对应记录,空指针异常");
        }
        return result;
    }

    public CommonResult handlerFallback(@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 blockException){
        Payment payment = new Payment(id, "null" );
        return new CommonResult<>( 445 , "blockHandler-sentinel 限流 , 无此流水 : blockException " +blockException.getMessage(),payment);
    }

}

(2)测试
访问 http://localhost:84/consumer/fallback/5
在这里插入图片描述
在异常数达到 2 以后
在这里插入图片描述
若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。

2.8.1.5 忽略属性 exceptionsToIgnore

(1)修改 CircleBreakerController,在注解 @SentinelResource 上增加 exceptionsToIgnore 属性,将异常 NullPointerException 忽略。

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

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler",exceptionsToIgnore = {NullPointerException.class})
    public CommonResult<Payment> fallback(@PathVariable("id") Long id){
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSql/" + id, CommonResult.class, id);
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常......");
        } else if(result.getData() == null){
            throw new NullPointerException("NullPointerException,该 ID 没有对应记录,空指针异常");
        }
        return result;
    }

    public CommonResult handlerFallback(@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 blockException){
        Payment payment = new Payment(id, "null" );
        return new CommonResult<>( 445 , "blockHandler-sentinel 限流 , 无此流水 : blockException " +blockException.getMessage(),payment);
    }

}

(2)测试

访问 http://localhost:84/consumer/fallback/5,服务会将空指针异常的信息直接返回给页面
SpringCloud Alibaba-Sentinel 实现熔断与限流_第71张图片
总结:exceptionsToIgnore 属性可以将指定的异常类型排除,使程序在执行过程中发生该异常类型时,不再执行兜底方法。

2.8.2 Sentinel 整合 OpenFeign

(1)pom.xml 引入 openfeign 依赖

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

注意此处需将 SpringCloud Alibaba 的版本回退到 2.1.0.RELEASE 版本,2.2.7 会有 jar 包冲突,具体暂时不清楚。

(2)application.yml 中激活 sentinel 对 openFeign 的支持

server:
  port: 84

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719

# 消费者将要去访问的微服务名称(注册成功进 nacos 的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider

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

(3)带@FeignClient注解的业务接口

@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
public interface PaymentService {

    @GetMapping("/paymentSql/{id}")
    public CommonResult<Payment> paymentSql(@PathVariable("id") Long id);
}

(4)PaymentFallbackService 类

@Component
public class PaymentFallbackService implements PaymentService{
    @Override
    public CommonResult<Payment> paymentSql(Long id) {
        return new CommonResult<>( 444 , " 服务降级返回 , 没有该流水信息 " , new Payment(id, "errorSerial......" ));
    }
}

(5)CircleBreakerController

@RestController
@Slf4j
public class CircleBreakerController {
   
    @Resource
    private PaymentService paymentService;

    @GetMapping("/consumer/paymentSql/{id}")
    public CommonResult<Payment> paymentSql(@PathVariable("id") Long id){
        return paymentService.paymentSql(id);
    }

}

(6)主启动类,添加@EnableFeignClients启动Feign的功能

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

(7)测试
访问 http://localhost:84/consumer/paymentSql/1
在这里插入图片描述
测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级,不会被耗死
在这里插入图片描述

2.9 规则持久化

现在,sentinel的所有规则都是内存存储,重启后所有规则都会丢失。在生产环境下,我们必须确保这些规则的持久化,避免丢失。

2.9.1 规则管理模式

规则是否能持久化,取决于规则管理模式,sentinel 支持三种规则管理模式:

  • 原始模式:Sentinel 的默认模式,将规则保存在内存,重启服务会丢失
  • pull 模式
  • push 模式

2.9.1.1 pull 模式

pull 模式:控制台将配置的规则推送到 Sentinel 客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第72张图片

2.9.1.2 push 模式

push 模式:控制台将配置规则推送到远程配置中心,例如 Nacos。Sentinel 客户端监听 Nacos,获取配置变更的推送消息,完成本地配置更新。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第73张图片

实现:

1、修改cloudalibaba-sentinel-service8401
(1)pom.xml


<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-datasource-nacosartifactId>
dependency>

(2) 配置nacos地址
在 cloudalibaba-sentinel-service8401 中的application.yml文件配置nacos地址及监听的配置信息:

spring:
  cloud:
    sentinel:
      datasource:
        flow:
          nacos:
            server-addr: localhost:8848 # nacos地址
            dataId: orderservice-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow # 还可以是:degrade、authority、param-flow

2、修改sentinel-dashboard源码
SentinelDashboard默认不支持nacos的持久化,需要修改源码。

解压

解压sentinel源码包:
在这里插入图片描述
然后并用IDEA打开这个项目,结构如下:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第74张图片

修改nacos依赖

在sentinel-dashboard源码的pom文件中,nacos的依赖默认的scope是test,只能在测试时使用,这里要去除:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第75张图片
将sentinel-datasource-nacos依赖的scope去掉:

<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-datasource-nacosartifactId>
dependency>
添加nacos支持

在sentinel-dashboard的test包下,已经编写了对nacos的支持,我们需要将其拷贝到main下。
SpringCloud Alibaba-Sentinel 实现熔断与限流_第76张图片

修改nacos地址

然后,还需要修改测试代码中的NacosConfig类:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第77张图片
修改其中的nacos地址,让其读取application.properties中的配置:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第78张图片
在sentinel-dashboard的application.properties中添加nacos地址配置:

nacos.addr=localhost:8848
配置nacos数据源

另外,还需要修改com.alibaba.csp.sentinel.dashboard.controller.v2包下的FlowControllerV2类:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第79张图片
让我们添加的Nacos数据源生效:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第80张图片

修改前端页面

接下来,还要修改前端页面,添加一个支持nacos的菜单。
修改src/main/webapp/resources/app/scripts/directives/sidebar/目录下的sidebar.html文件:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第81张图片
将其中的这部分注释打开:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第82张图片
修改其中的文本:
在这里插入图片描述

重新编译、打包项目

运行IDEA中的maven插件,编译和打包修改好的Sentinel-Dashboard:
SpringCloud Alibaba-Sentinel 实现熔断与限流_第83张图片

启动

启动方式跟官方一样:

java -jar sentinel-dashboard.jar

如果要修改nacos地址,需要添加参数:

java -jar -Dnacos.addr=localhost:8848 sentinel-dashboard.jar

你可能感兴趣的:(Sentinel,SpringCloud,Alibaba,spring,cloud,sentinel,java)