Spring Cloud Alibaba(03)——Sentinel实现熔断与限流

535 阅读27分钟

1、Sentinel概述

在这里插入图片描述

Sentinel 是什么?

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

Sentinel 具有以下特征:

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

Sentinel 的主要特性:

Sentinel-features-overview

以上内容摘自Sentinel GitHub官网:github.com/alibaba/Sen…

Sentinel v1.8 中文文档手册

2、Sentinel下载安装运行

1、下载安装

下载地址:github.com/alibaba/Sen…

在这里插入图片描述

2、运行

Sentinel 分为两个部分:

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

运行jar包:java -jar sentinel-dashboard-1.8.0.jar

在这里插入图片描述

访问请求:http://localhost:8080

在这里插入图片描述

用户名和密码都为sentinel,点击登录

在这里插入图片描述

Sentinel下载安装运行成功。

3、初始化演示工程

1、启动nacos

在这里插入图片描述

2、新建模块

  1. 创建cloudalibaba-sentinel-service8401模块

  2. 导入pom依赖

    <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <!--持久化-->
            <dependency>
                <groupId>com.alibaba.csp</groupId>
                <artifactId>sentinel-datasource-nacos</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
        </dependencies>
    
  3. 编写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地址,通过8080监控8401
            port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
    
    management:
      endpoints:
        web:
          exposure:
            include: '*'
    
  4. 创建主启动类

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

    @RestController
    public class FlowLimitController {
    
        @GetMapping(value = "/testA")
        public String testA(){
            return "------testA";
        }
    
        @GetMapping(value = "/testB")
        public String testB(){
            return "------testB";
        }
    }
    
  6. 启动 sentinel-dashboard

    java -jar sentinel-dashboard-1.8.0.jar
    
  7. 启动 cloudalibaba-sentinel-service8401模块

  8. 因为Sentinel采用的是懒加载,所以需要我们访问一次请求,sentinel-dashboard 才能监控到8401服务

    http://localhost:8401/testA

    http://localhost:8401/testA

  9. 再刷新sentinel-dashboard 界面

在这里插入图片描述

4、流控规则

4.1、流控规则简介

流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:

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

4.2、流控模式

4.2.1、直接

QPS直接失败

Sentinel的流控模式代表的流控的方式,默认【直接】;   上面的/testA接口的流控,QPS单机阀值为1,代表每秒请求不能超出1,要不然就做流控处理,处理方式直接调用失败;   调用/testA,慢一点请求,正常返回;快速请求几次,超过阀值;接口返回了Blocked by Sentinel (flow limiting),代表被限流了

配置及说明:

在这里插入图片描述

测试:

在这里插入图片描述

线程数直接失败

Sentinel 并发线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目,如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。

在这里插入图片描述

1s内超过一个线程访问,就会直接失败。

测试:

http://localhost:8401/testA请求加一秒延迟,然后开启两个页面进行访问:

@GetMapping(value = "/testA")
public String testA(){

    try {
        TimeUnit.MILLISECONDS.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "------testA";
}

4.2.2、关联

当与资源A关联的资源B达到阈值,就限流资源A自己。比如在分布式系统中,支付服务与订单服务关联,这样当支付服务太过频繁时,订单服务的请求就被限流,禁止提交订单。

配置与说明:

在这里插入图片描述

测试:用jmeter高并发压/testB接口

在这里插入图片描述

上图设置意味着每秒发送5次请求,即QPS=5。超过流控阈值。 在这里插入图片描述

jmeter启动压测,浏览器访问 http://localhost:8401/testB,会发现资源 /testA 已经被限流:

在这里插入图片描述

4.2.3、链路

链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流;它的功能有点类似于针对 来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度 更细。

仅对同一个service的某个入口进行流控,其他入口不做控制。

4.3、流控效果

4.3.1、快速失败

直接快速失败方式是==默认的流量控制方式==,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。

4.2.2、WarmUp

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

在这里插入图片描述

==根据codeFactor(默认3)的值,从(阀值/codeFactor)为初始阀值,经过预热时长,才到达设置的QPS的阀值==

上面是什么意思呢?我们来举个案例,阀值为10,预热时长设置5秒。

在这里插入图片描述

上图意思为:我希望资源/testA 最终一秒能承受10个QPS,就是单机阈值为10,但最开始只给资源/testA 承受10/3个QPS,就是初始阈值约为3,五秒后,才让阈值慢慢升高到10。

测试:

访问请求:http://localhost:8401/testA,快速刷新,一开始会出现报错,但5秒后,就算快速刷新也不会报错了。

应用场景:

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

4.2.3、匀速排队

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

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

注意:匀速排队模式暂时不支持 QPS > 1000 的场景。

配置及说明:

在这里插入图片描述

每访问一次/testA,后台会输出一次日志

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

测试:再次利用jmeter对http://localhost:8401/testA进行压测:设置20个线程,每秒执行一次,无限循环

启动jmeter,查看后台输出:

在这里插入图片描述

我们可以发现testA每隔1秒执行一次,这么多的请求没有被拒绝,而且进入的排队。

排队的应用场景是什么呢?

比如有时候系统在某一个时刻会出现大流量,之后流量就恢复稳定,可以采用这种排队模式,大流量来时可以让流量请求先排队,等恢复了在慢慢进行处理

5、降级规则

5.1、降级规则简介

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出

Sentinel在1.8.0版本对熔断降级做了大的调整,可以定义任意时长的熔断时间,引入了半开启恢复支持。

降级策略

在这里插入图片描述

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。==当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。==经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。RT最大4900.
  • 异常比例 (ERROR_RATIO):==当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。==经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):==当单位统计时长内的异常数目超过阈值之后会自动进行熔断==。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

5.2、降级策略实战

5.2.1、慢调用比例

慢调用:指耗时大于阈值RT的请求称为慢调用,阈值RT由用户设置

最小请求数:允许通过的最小请求数量,在最小请求数量内不发生熔断,由用户设置

img

配置及说明:

在这里插入图片描述

上面配置说明:

当请求数大于最小请求数5,并且慢调用的比率大于比例阈值0.6,则发生熔断,熔断时长1s。

当熔断过了定义的熔断时长,状态由熔断(OPEN)变为探测(HALFOPEN)。

如果接下来的一个请求最大响应时间小于最大RT200ms,说明慢调用已经恢复,结束熔断,状态由探测(HALF_OPEN)变更为关闭(CLOSED)

如果接下来的一个请求最大响应时间大于最大RT200ms,说明慢调用未恢复,继续熔断,熔断时长保持一致

测试

增加一个方法:该方法的响应时间为1s,大于200ms,所以调用该方法为慢调用

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

使用jmeter压测:配置1秒10个线程去访问 localhost:8401/testD,大于最小请求数

在这里插入图片描述

在这里插入图片描述

启动jmeter测试计划,可以看到 localhost:8401/testD已被熔断:

在这里插入图片描述

5.2.2、异常比例

通过计算异常比例与设置阈值对比的一种策略。

当资源的每秒请求数大于等于最小请求数,并且异常总数占通过量的比例超过比例阈值时,资源进入降级状态。

img

配置及说明

在这里插入图片描述

说明:

当请求数大于5,并且异常比例大于0.2时,发生熔断,熔断时长微

秒,就是后面2秒内服务不可使用。

当超过熔断时长时,由熔断(OPEN)转为探测(HALFOPEN)

如果接下来的一个请求未发生错误,说明应用恢复,结束熔断,状态由探测(HALF_OPEN)变更为关闭(CLOSED)

如果接下来的一个请求继续发生错误,说明应用未恢复,继续熔断,熔断时长保持一致

测试

增加一个方法:该方法发生运行时异常,

@GetMapping(value = "/testC")
public String testC(){
    log.info("=====testC=====");
    int age = 10/0;
    return "------testC";
}

使用jmeter压测:配置1秒10个线程去访问 localhost:8401/testC

在这里插入图片描述

在这里插入图片描述

启动jmeter测试计划,可以看到 localhost:8401/testC已被熔断:

在这里插入图片描述

当我们关闭jmeter,单独访问这个请求时,因为不满足降级条件(请求数不满足),所以不会使用降级策略,会直接跳转到Error Page。

5.2.3、异常数

通过计算发生异常的请求数与设置阈值对比的一种策略

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

img

配置及说明:

在这里插入图片描述

说明:

近一分钟内,当请求数大5并且异常数量大于5时触发熔断,熔断时长为70s。

当超过熔断时长时,由熔断(OPEN)转为探测(HALFOPEN)

如果接下来的一个请求未发生错误,说明应用恢复,结束熔断,状态由探测(HALF_OPEN)变更为关闭(CLOSED)

如果接下来的一个请求继续发生错误,说明应用未恢复,继续熔断,熔断时长保持一致

测试

访问请求 localhost:8401/testC 5次,前五次为错误页面,当第6次时,触发熔断。

6、热点规则

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

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

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

img

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

添加方法:

对于 @SentinelResource 注解方式定义的资源,若注解作用的方法上有参数,Sentinel 会将它们作为参数传入

@GetMapping(value = "/testE")

@SentinelResource(value = "testE",blockHandler = "deal_hotKey") //设置兜底的方法,当不符合热点配置规则,就使用兜底的方法,当程序有运行时异常时,还是走运行时异常
public String hotKey(@RequestParam(value = "p1",required = false) String p1,
                     @RequestParam(value = "p2",required = false) String p2){

    return "=====testE=====";

}

public String deal_hotKey(String p1, String p2, BlockException exception){
    return "=====执行了兜底方法;deal_hotKey=====";
}

配置及说明:

在这里插入图片描述

说明:当参数p1每秒访问超过3次,就对包含参数p1的资源进行限流,限流时间1s

测试:

只携带p1参数进行测试

正常访问资源:http://localhost:8401/testE?p1=abc

在这里插入图片描述

当一秒内访问资源超过三次时:

在这里插入图片描述

携带p1和p2测试

正常访问资源:http://localhost:8401/testE?p1=abc&p2=def

在这里插入图片描述

当一秒内访问资源超过三次时:照样执行了兜底的方法

在这里插入图片描述

只携带p2参数进行测试,因为热点配置规则里对参数p2没有做相关的配置,所以p2访问没有限制。

参数例外项

参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型

规则及说明:

在这里插入图片描述

说明:当参数p1的每秒访问大于3时,就对包含参数p1的资源进行限流,限流时间1s,但是当参数p1的值为9537时,它的QPS阈值可以为1000,之前设置的单机阈值3无效。

测试:访问请求:http://localhost:8401/testE?p1=9527&p2=def 只要QPS不到1000访问,资源可以正常访问。

7、系统规则

系统自适应限流

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

系统保护规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 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 达到阈值即触发系统保护。

8、@SentinelSource配置

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

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

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

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

  • fallback / fallbackClass: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 逻辑中,而是会原样抛出。

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

8.1、按资源名称限流

1、在cloudalibaba-sentinel-service8401模块中加入pom依赖:

<dependency>
    <groupId>com.cheng.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

2、增加一个controller

@RestController
public class RateLimitController {

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

3、测试

使用默认的流控模式

在这里插入图片描述

访问请求:http://localhost:8401/byResource,一秒内访问一次

在这里插入图片描述

一秒内多次访问:服务限流,返回自定义的限流信息

在这里插入图片描述

8.2、按url地址限流

增加一个方法:

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

配置流控规则:

在这里插入图片描述

测试

访问请求:http://localhost:8401/rateLimit/byUrl,一秒内访问一次

在这里插入图片描述

一秒内多次访问,服务限流,返回系统自带的限流信息

在这里插入图片描述

8.3、客户自定义限流处理逻辑

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

public class CustomerBlockHandler {

    public static CommonResult handlerException1(BlockException exception){
        return new CommonResult(444,"按客户自定义 方案一");
    }

    public static CommonResult handlerException2(BlockException exception){
        return new CommonResult(444,"按客户自定义 方案二");
    }

}

增加方法:

@GetMapping(value = "/rateLimit/customerBH")
@SentinelResource(value = "customerBlockHandler",
        blockHandlerClass = CustomerBlockHandler.class,
        blockHandler = "handlerException2") //选择CustomerBlockHandler类中的handlerException2方法做限流处理
public CommonResult customerBlockHandler(){
    return new CommonResult(200,"客户自定义测试ok",new Payment(2021L,"serial003"));
}

增加流控规则:

在这里插入图片描述

测试

访问请求:http://localhost:8401//rateLimit/customerBH,一秒内访问一次

在这里插入图片描述

一秒内多次访问,服务限流,返回自定义的且自己选择的限流信息

在这里插入图片描述

9、服务熔断功能

1、整合Ribbon

服务提供者9003和9004

1、新建两个服务提供者模块:

cloudalibaba-provide-payment9003

cloudalibaba-provide-payment9004

这两个模块除了端口号不同,其他内容一样。

2、导入pom依赖

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <dependency>
        <groupId>com.cheng.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
</dependencies>

3、编写yml配置文件

server:
  port: 9003 // 或9004

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # 配置Nacos的地址
#暴露端点
management:
  endpoints:
    web:
      exposure:
        include: '*'

4、主启动类

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

5、编写业务类

@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(value = "/paymentSQL/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Integer id){
        Payment payment = hashMap.get(id);
        CommonResult<Payment> result = new CommonResult(200, "from mysql serverport= " + serverPort, payment);
        return result;
    }
}

6、测试

启动 cloudalibaba-provide-payment9003 和 cloudalibaba-provide-payment9004 模块

访问请求:http://localhost:9003/paymentSQL/3

在这里插入图片描述

访问请求:http://localhost:9004/paymentSQL/1

在这里插入图片描述

服务消费者84

1、创建 cloudalibaba-consumer-nacos-order84 模块

2、导入pom依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>

    <dependency>
        <groupId>com.cheng.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3、编写yml配置文件

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

4、主启动类

@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain84{

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

5、编写配置类,开启ribbon负载均衡

@Configuration
public class ApplicationContextConfig{

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

@SentinelResource没有属性

6、编写业务类

@RestController
@Slf4j
public class CircleBreakerController {
    
    public 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 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;
}

测试:

访问请求:http://localhost:84/consumer/fallback/1,访问的是9003

在这里插入图片描述

再访问一次,访问的是9004

在这里插入图片描述

之后持续访问,将在9003和9004之间轮换,实现了ribbon轮询的负载机制。

当我们请求携带的参数不是1,2,3时,将会出现对应的Error 页面、

@SentinelResource 只增加 fallback 属性

给@SentinelResource增加fallback属性,并增加处理异常的方法:

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

    @RequestMapping("/consumer/fallback/{id}")
     //fallback只负责业务异常
    @SentinelResource(value = "fallback",fallback = "handlerFallback") 
    public CommonResult<Payment> fallback(@PathVariable 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;
    }
    //fallback
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
    }
}

测试fallback方法是否生效:

访问请求:http://localhost:84/consumer/fallback/4,

在这里插入图片描述

访问请求:http://localhost:84/consumer/fallback/6

在这里插入图片描述

这时没有出现Error Page,返回了我们自定义的异常信息

@SentinelResource 只增加 blockHandler 属性

@SentinelResource只增加blockHandler属性,并增加blockHandler方法:

@RestController
@Slf4j
public class CircleBreakerController {
    public static final String SERVICE_URL = "http://nacos-payment-provider";
    @Resource
    private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
 
    @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
public CommonResult<Payment> fallback(@PathVariable 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;
}
    //blockHandler
    public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
    }
}

在sentinel控制台配置降级规则:

在这里插入图片描述

在一分钟内,如果请求数大于5并且异常数大于3,触发服务降级。

测试

访问请求:http://localhost:84/consumer/fallback/4http://localhost:84/consumer/fallback/6 各一次:

出现异常页面

在这里插入图片描述

在这里插入图片描述

持续访问请求:http://localhost:84/consumer/fallback/4http://localhost:84/consumer/fallback/6 ,超过三次后:

在这里插入图片描述

在这里插入图片描述

触发降级规则,服务熔断,返回自定义的处理信息。

@SentinelResource 同时增加 blockHandler 和 fallback 属性

@SentinelResource 同时增加 blockHandler 和 fallback 属性,并加上相应的处理方法:

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

    @RequestMapping("/consumer/fallback/{id}")
  @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler")
    public CommonResult<Payment> fallback(@PathVariable 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;
    }
    //fallback
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
    }
    //blockHandler
    public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
    }
}

配置sentinel 流控规则:

在这里插入图片描述

测试

一秒内访问一次请求:http://localhost:84/consumer/fallback/4 ,因为此时的QPS没有大于1,因此没有破坏sentinel的限流规则,所以是返回fallback的异常信息。

在这里插入图片描述

但当一秒内访问次数大于1时,就破坏了sentinel的限流规则,返回的是blockHandler处理的异常信息

在这里插入图片描述

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

exceptionsToIgnore属性

在@SentinelResource 注解中使用 exceptionsToIgnore 属性:

exceptionsToIgnore = {IllegalArgumentException.class} //可以配置多个

加了exceptionsToIgnore后,IllegalArgumentException 异常将不会被fallback 方法处理,会直接返回Error Page。

2、整合OpenFeign

1、在cloudalibaba-consumer-nacos-order84 模块添加openfeign 的依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2、在84模块的yml配置文件添加如下配置:

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

3、主启动类增加注解@EnableFeignClients

4、编写feign接口

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

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

}

5、编写全局降级方法

@Component
public class PaymentFallbackService implements PaymentService{

    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(444,"服务异常,被 PaymentFallbackService 降级处理",new Payment(id,"error"));
    }
}

需要在@FeignClient中配置fallback。

6、在controller中用openfeign调用服务

@Resource
private PaymentService paymentService;

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

7、测试

访问请求:http://localhost:84/consumer/paymentSQL/1

在这里插入图片描述

再访问一次

在这里插入图片描述

关闭9003和9004服务,再次访问请求:http://localhost:84/consumer/paymentSQL/1

在这里插入图片描述

被全局降级方法处理。

3、熔断框架比较

在这里插入图片描述

10、规则持久化

在上面的案例中,如果服务关闭了,那么服务对应的流控规则也会消失。每次重启服务,就需要重新配置规则,这显然不符合真实的开发需要。所以我需要将配置规则持久化。

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

1、8401模块中添加pom依赖:

<!--持久化-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

2、在yml配置文件中添加nacos数据源配置

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848  #nacos服务中心地址
    sentinel:
      transport:
        dashboard: localhost:8080  #配置Sentinel dashboard地址,通过8080监控8401
        port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

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

3、在nacos中添加业务规则配置

在这里插入图片描述

配置说明:

resource:资源名称;

limitApp:来源应用;

grade:阈值类型,0表示线程数,1表示QPS;

count:单机阈值;

strategy:流控模式,o表示直接,1表示关联,2表示链路;

controlBehavior:流控效果,0表示快速失败,1表示Warm up,2表示排队等待;

clusterMode:是否集群。

4、测试

关闭8401服务,看sentinel中是否还有流控规则:

在这里插入图片描述

流控规则消失了。

再重启8401服务,此时sebtinel中没有流控规则,

当我们访问一次http://localhost:8401/rateLimit/byUrl请求,sentinel就监控到/rateLimit/byUrl,流控规则就会自动出现了:

在这里插入图片描述