欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

三、Sentinel实现熔断与限流

程序员文章站 2022-03-23 16:23:39
...

一、概述

1、Sentinel介绍

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

2、Sentinel特征

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

三、Sentinel实现熔断与限流

3、Sentinel的开源生态

三、Sentinel实现熔断与限流

4、相关地址

1、官网地址
2、中文官网
3、参考文档

二、启动Sentinel控制台

1、组成

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

2、下载地址

下载地址,推荐使用自带浏览器下载,下载速度会很快

三、Sentinel实现熔断与限流

3、启动

1、启动前提
  • jdk环境正常
  • 8080端口不能被占用
2、cmd进入安装包位置并执行启动命令
nohup java -jar -Xms256m -Xmx256m -XX:PermSize=512M -XX:MaxPermSize=512m sentinel-dashboard-1.7.0.jar &
3、访问Sentinel管理界面
  • 地址:http://localhost:8080
  • 用户名和密码都为:sentinel

三、Sentinel实现熔断与限流

三、Sentinel快速上手

1、新建一个Module

项目名: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-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>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>

    <dependency>
        <groupId>com.itan</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

3、yml配置

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        # server-addr: 192.168.241.135:8848/ #配置Nacos地址
        # 由于nacos配置成集群了,因此需要换成nginx监听的1111端口
        server-addr: 192.168.241.135:1111
    sentinel:
      transport:
        # sentinel 仪表盘地址
        dashboard: localhost:8080
        # 默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口
        port: 8719
      # eager: true # 取消延迟加载(默认是延迟加载的)

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

4、启动类

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

5、业务类

@RestController
@RequestMapping("sentinel")
public class Sentinel8401Controller {
    @GetMapping("getA")
    public String testA(){
        return "--------testA";
    }

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

6、测试

1、启动nacos集群
2、启动sentinel
3、启动Sentinel8401
4、查看sentinel控制台,发现什么也没有,说明Sentinel采用的是懒加载,多执行几次请求,刷新控制台

三、Sentinel实现熔断与限流

注:sentinel的监控台一开始是没有东西的,需要对监控的服务发起请求后才会出现。

四、流控规则

1、流控规则说明

三、Sentinel实现熔断与限流

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

2、流控模式之直接模式

默认方式,当达到阈值直接快速失败
1、配置

三、Sentinel实现熔断与限流

说明:表示在1秒钟内调用一次/sentinel/getB接口就OK,若超过1次,就直接失败,报默认错误

三、Sentinel实现熔断与限流

2、调用/sentinel/getB接口慢一点,发现正常返回。
3、快速调用接口,超过阈值,接口返回了Blocked by Sentinel (flow limiting),代表被限流了。

三、Sentinel实现熔断与限流

三、Sentinel实现熔断与限流

3、流控模式之关联模式

当关联的资源达到阈值时,就限流自己。当与A关联的资源B达到阈值后,就限流自己,B惹事了,但是A挂了,B没事。
1、配置

三、Sentinel实现熔断与限流

说明:当关联资源/sentinel/getB的QPS阈值超过1时,就限制访问/sentinel/getA,当关联资源达到阈值就限制配置的资源名
2、单独postman调用/sentinel/getB接口,再调用getA接口发现正常
3、多线程下模拟并发密集访问getB接口,再访问getA接口,发现报错Blocked by Sentinel (flow limiting),代表被限流了。

三、Sentinel实现熔断与限流

三、Sentinel实现熔断与限流

4、流控模式之链路模式

只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流,api级别的针对来源)
1、进入簇点链路下,查看/sentinel/getA的资源入口是sentinel_web_servlet_context

三、Sentinel实现熔断与限流

2、添加流控,配置链路入口资源为sentinel_web_servlet_context

三、Sentinel实现熔断与限流

3、当频繁访问接口/sentinel/getA时,发现报错Blocked by Sentinel (flow limiting),代表被限流了。

三、Sentinel实现熔断与限流

5、流控效果之快速失败

上述使用的都是快速失败,当QPS达到阈值就直接抛出异常Blocked by Sentinel (flow limiting)
源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

6、流控效果之预热

1、官网解释sentinel限流-冷启动-预热
2、公式:阈值除以coldFactor(默认值为3),即请求QPS从threshold/3开始,经预热时长逐渐升至设定的QPS阈值。
3、源码:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

三、Sentinel实现熔断与限流

4、添加流控,选择Warm Up,设置预热时长

三、Sentinel实现熔断与限流

说明:设置阈值为10,系统初始化的QPS阈值为10/3≈3,即阈值刚开始为3,经过预热时长5秒钟后,阈值才会慢慢上升到10
5、快速刷新,刚开始发现抛出异常,后续没有抛出异常信息了,正常了

三、Sentinel实现熔断与限流

6、引用场景:秒杀系统在开启的瞬间,会有很多流量上来,很有可能会让系统宕机,预热方式是为了保护系统,慢慢的把流量放进来,慢慢把阈值增涨到设定的阈值。

7、流控效果之排队等待

1、官网解释流量控制

三、Sentinel实现熔断与限流

2、源码:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
3、删除之前配置的流控,防止出现干扰,再新建流控

三、Sentinel实现熔断与限流

说明:/sentinel/getA每秒一次请求,超过阈值的话,就排队等待,等待的超时时间为20000ms
4、密集访问/sentinel/getA接口,发现每一次访问都没有抛出异常,说明在排队等待

三、Sentinel实现熔断与限流

5、将超时时间设置为2ms,连续访问接口,发现抛出异常信息

三、Sentinel实现熔断与限流

五、降级规则

1、概述

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

三、Sentinel实现熔断与限流

现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
简述:Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)。
Sentinel的断路器是没有半开状态的,半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考Hystrix

2、基本介绍

三、Sentinel实现熔断与限流

RT(平均响应时间,秒级):
  • 平均响应时间超出阈值且在时间窗口内通过的请求数 >= 5,两个条件同时满足后触发降级,时间窗口结束后关闭降级。
  • RT最大4900ms(更大的需要通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置)。
异常比例(秒级):
  • QPS >= 5且异常比例(秒级统计)超过阈值时,触发降级,时间窗口结束后,关闭降级。
异常数(分钟级):
  • 异常数(分钟统计)超过阈值时,触发降级,时间窗口结束后,关闭降级。

3、降级策略之RT

1、平均响应时间(DEGRADE_GRADE_RT)说明

当1s内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值(count 以ms为单位),那么在接下来的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地熔断(【抛出DegradeException)。

2、业务类中新加textC方法

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

3、配置降级规则

三、Sentinel实现熔断与限流

4、jmeter压测

1、创建线程组

三、Sentinel实现熔断与限流

2、创建HTTP请求

三、Sentinel实现熔断与限流

3、执行测试并在浏览器访问getC接口,接口返回了Blocked by Sentinel (flow limiting),代表被限流了。

三、Sentinel实现熔断与限流

4、说明
按照上面的配置,在一秒钟内起10个线程(满足每秒QPS大于5个了)调用getC接口,如果响应时间超出(希望200ms能够处理完)还没有处理完,在未来1秒钟的时间窗口内,断路器打开,微服务不可用;后续停止jmeter,没有这么大的访问量了,断路器关闭,微服务恢复,可以访问。

4、降级策略之异常比例

1、异常比例(DEGRADE_GRADE_EXCEPTION_RATIO)说明

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

2、在上述testC方法中制造一个异常

@GetMapping("getC")
public String testC(){
    log.info("testC 测试异常比例");
    int i = 10 / 0;
    return "------testC";
}

3、配置降级规则

三、Sentinel实现熔断与限流

说明:异常比例超过20%,并且每秒钟 >=5 个请求的时候,在接下来的时间窗口3内服务不可用了。

4、jmeter压测

1、配置同上不变
2、执行测试并在浏览器访问getC接口,接口返回了Blocked by Sentinel (flow limiting),代表被限流了。

三、Sentinel实现熔断与限流

3、当停止jmeter后,再次访问,发现报错Error

三、Sentinel实现熔断与限流

4、说明
按照上面的配置,当单独访问接口的时候,报错(by zero),并且是调用一次报错一次,开启jmeter后,直接高并发发送请求,1秒钟内起10个线程(满足QPC每秒大于5个了)调用getC接口,并且异常比例也超过0.2,断路器打开,微服务不可用了,不再报错error而是服务降级了。

5、降级策略之异常数

1、异常数(DEGRADE_GRADE_EXCEPTION_COUNT)说明

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

2、业务类中新加一个getD方法

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

3、配置降级规则

三、Sentinel实现熔断与限流

4、测试

1、访问次数小于设置的异常数阈值,发现报错Error

三、Sentinel实现熔断与限流

2、访问次数大于设置的异常数阈值,接口返回了Blocked by Sentinel (flow limiting),代表被限流了。

三、Sentinel实现熔断与限流

六、热点规则

1、概述

1、官网:热点参数限流
2、解释热点:即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

2、兜底方法

  • 系统默认的方法,限流出问题后,都用sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
  • 自定义方法,类似Hystrix中@HystrixCommond,某个方法出问题了,就能找到对应的兜底方法,在sentinel中就要使用到@SentinelResource
  • 源码:com.alibaba.csp.sentinel.slots.block.BlockException

3、@SentinelResource注解介绍

作用:用于定义资源,并提供了AspectJ的扩展用于自动定义资源、处理BlockException等
属性名 是否必填 说明
value 资源名称。(必填项,需要通过 value 值找到对应的规则进行配置)
entryType entry类型,标记流量的方向,取值IN/OUT,默认是OUT
blockHandler 处理BlockException的函数名称(可以理解为对Sentinel的配置进行方法兜底)。函数要求:
1.必须是 public 修饰
2.返回类型与原方法一致
3. 参数类型需要和原方法相匹配,并在最后加 BlockException 类型的参数。
4. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 blockHandlerClass ,并指定blockHandlerClass里面的方法。
blockHandlerClass 存放blockHandler的类
对应的处理函数必须public static修饰,否则无法解析,其他要求:同blockHandler。
fallback 用于在抛出异常的时候提供fallback处理逻辑(可以理解为对Java异常情况方法兜底)
fallback函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求:
1.返回类型与原方法一致
2.参数类型需要和原方法相匹配,Sentinel 1.6开始,也可在方法最后加 Throwable 类型的参数。
3.默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定fallbackClass里面的方法。
fallbackClass 存放fallback的类
对应的处理函数必须static修饰,否则无法解析,其他要求:同fallback。
defaultFallback 用于通用的 fallback 逻辑
默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求:
1.返回类型与原方法一致
2.方法参数列表为空,或者有一个 Throwable 类型的参数。
3.默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定 fallbackClass 里面的方法。
exceptionsToIgnore 指定排除掉哪些异常。
排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。
exceptionsToTrace 需要trace的异常

4、配置热点key规则

1、业务类新建一个方法

三、Sentinel实现熔断与限流

2、sentinel配置热点规则

三、Sentinel实现熔断与限流

说明:第一个参数的阈值超过每秒钟一个,就会违背配置规则,就会由sentinel方法兜底。

3、测试

1、接口传入p1参数且每秒点击一次的时候能正常返回接口的内容,连续点击多次,返回的是兜底方法的内容

三、Sentinel实现熔断与限流

2、接口参数都传且每秒点击一次的时候能正常返回接口的内容,连续点击多次,返回的是兜底方法的内容

三、Sentinel实现熔断与限流

3、接口不传p1参数,无论点击多少次都能正常返回接口内容

三、Sentinel实现熔断与限流

说明:按照上面配置,当资源的第一个参数的阈值超过每秒钟一个,就会违背配置规则,就会由sentinel方法兜底。

5、参数例外项

1、说明

1、通过上面案例可知,当参数索引为0(也就是第一个参数p1)的QPS超过1秒1次点击后马上被限流。
2、通常情况下,超过1秒钟后达到阈值1后马上就被限流。如果是期望p1参数当它是某一个特殊值时,它的限流和平时不一样,比如说当p1的值为66时,它的阈值可以达到200,那么这个时候它就要用到参数例外项。

2、配置参数例外项

三、Sentinel实现熔断与限流

说明:参数索引下标为0(也就是第一个参数p1),通常QPS超过1马上就被限流,但是结合例外项,当参数值为66时,QPS阈值会达到200
注意:参数必须是基本类型或者String

3、测试

1、当p1参数值不为66时,1秒访问1次接口返回正常,但是快速访问接口,就会被限流。返回兜底方法内容。
2、当p1参数值为66时,无论1秒钟访问多少次接口,都能拿到正常内容,因为QPS阈值是200。

三、Sentinel实现熔断与限流

4、补充说明

倘若我们在代码中手动制造一个异常,比如:int i = 10 / 0,发现就会报错,这是因为@SentinelResource处理的是Sentinel控制台配置的违规情况,才会有blockHandler方法配置的兜底处理,int i = 10 / 0这个是java运行时报出的运行时异常RunTimeException,blockHandler不会管,但是可以配置fallback来对程序异常进行兜底。

七、系统规则

1、概述

官网:系统自适应限流
系统自适应限流说明:Sentinel 系统自适应限流从应用整体维度对应用入口流量进行控制(只对入口流量生效),结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护的目的:
  • 保证系统不被拖垮
  • 在系统稳定的前提下,保持系统的吞吐量
入口流量:指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

2、系统规则的模式

  • 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 达到阈值即触发系统保护。

3、配置

1、配置一个系统规则

三、Sentinel实现熔断与限流

说明:当整个系统(是全局生效,不再针对某一个接口)的QPS达到阈值1的时候,就会被限流

4、测试

快速访问接口,发现被限流了,访问其他接口也限流了,说明是针对全局的

三、Sentinel实现熔断与限流

八、@SentinelResource

1、按资源名称限流及问题发现

1、在8401中新建一个业务类

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

2、添加流控规则

三、Sentinel实现熔断与限流

说明:表示1秒钟内查询次数大于1,就跑到我们自定义的处流,限流

3、测试

1、一秒钟点击一下OK
2、疯狂点击,返回了自己定义的限流处理信息,限流发送

三、Sentinel实现熔断与限流

4、问题

当关闭8401服务后,发现sentinel控制台没有流控规则了,说明流控规则是临时的

2、按照Url地址限流及问题发现

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息Blocked by Sentinel (flow limiting)

1、添加流控规则

三、Sentinel实现熔断与限流

2、测试

1、一秒钟点击一下OK
2、疯狂点击,会返回sentinel自带的限流处理结果

三、Sentinel实现熔断与限流

3、上面兜底方法的缺点

1、系统默认的兜底方法,没有体现出自己的业务需求。
2、自定义的兜底方法和业务代码耦合在一起,不直观。
3、每个业务方法都添加一个兜底方法,代码会膨胀。
4、全局统一的处理方法没有体现。

4、自定义限流处理逻辑

1、新建一个类用于自定义限流处理逻辑

package com.itan.handler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.itan.common.CommonResult;

public class CustomerBlockHandler {
    public static CommonResult handlerException1(BlockException exception){
        return new CommonResult("20000","自定义的限流处理类----CustomerBlockHandler----1");
    }
    
    public static CommonResult handlerException2(BlockException exception){
        return new CommonResult("20000","自定义的限流处理类----CustomerBlockHandler----2");
    }
}

2、新建一个controller方法

@GetMapping("/rateLimit/customerBlockHandler")
//当QPS超过阈值,就会找到blockHandlerClass对应的类中blockHandler对应的方法名进行兜底处理
@SentinelResource(value = "customerBlockHandler",
                  blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")
public CommonResult customerBlockHandler() {
    return new CommonResult("200","按客戶自定义",new Payment(200,"serial003"));
}

3、配置限流规则

三、Sentinel实现熔断与限流

4、测试

疯狂点击,发现返回自定义的兜底方法的内容

三、Sentinel实现熔断与限流

九、整合Ribbon

1、新建两个服务提供者

cloudalibaba-provider-payment9003和cloudalibaba-provider-payment9004

1、pom配置

<dependencies>
    <!--nacos服务发现-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <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>
    <!--日常通用jar包配置-->
    <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>
    <!-- 引入自己定义的api通用包 -->
    <dependency>
        <groupId>com.itan</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
        <scope>compile</scope>
    </dependency>
    <!-- 引入fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
</dependencies>

2、yml配置

server:
  port: 9003 # 9004服务设置端口为9004

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        #server-addr: 192.168.241.135:8848/ #配置Nacos地址
        # 由于之前做了nacos集群,使用nginx做负载均衡,所以换成nginx的1111端口,做集群
        server-addr: 192.168.241.135:1111

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

3、启动类

@SpringBootApplication
@EnableDiscoveryClient //用于服务发现
public class Payment9003 {
    public static void main(String[] args) {
        SpringApplication.run(Payment9003.class, args);
    }
}

4、业务类

package com.itan.controller;

import com.itan.common.CommonResult;
import com.itan.entity.Payment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;

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

    public static HashMap<Integer, Payment> hashMap = new HashMap<>();
    static{
        hashMap.put(1,new Payment(1,"28a8c1e3bc2742d8848569891fb42181"));
        hashMap.put(2,new Payment(2,"bba8c1e3bc2742d8848569891ac32182"));
        hashMap.put(3,new Payment(3,"6ua8c1e3bc2742d8848569891xt92183"));
    }

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

5、启动测试

接口地址:http://localhost:9003/payment/1

2、新建一个服务消费者

cloudalibaba-consumer-nacos-order82
由于nacos自带Ribbon依赖,所以不需要再添加Ribbon依赖

1、配置pom

<dependencies>
    <!--nacos服务发现-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--sentinel依赖-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <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>
    <!--日常通用jar包配置-->
    <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>
    <!-- 引入自己定义的api通用包 -->
    <dependency>
        <groupId>com.itan</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
        <scope>compile</scope>
    </dependency>
    <!-- 引入fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
</dependencies>

2、配置yml

server:
  port: 82


spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        #server-addr: 192.168.241.135:8848/ #配置Nacos地址
        # 换成nginx的1111端口,做集群
        server-addr: 192.168.241.135:1111
    sentinel:
      transport:
        # sentinel 控制台地址
        dashboard: localhost:8080
        # 默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口
        port: 8720
      eager: true # 取消延迟加载(默认是延迟加载的)

# 服务调用地址
url:
  nacos-user-service: http://nacos-payment-provider

3、主启动类

@EnableDiscoveryClient //用于服务发现
@SpringBootApplication
public class Consumer82 {
    public static void main(String[] args) {
        SpringApplication.run(Consumer82.class,args);
    }
}

4、配置类

@Configuration
public class Consumer82Config {
    @Bean
    @LoadBalanced //负载均衡,轮询方式
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

5、业务类

具体测试具体配置

3、测试

1、启动nacos注册中心
2、启动sentinel
3、启动两个服务提供者

1、服务消费者中无任何配置

@RestController
@RequestMapping("consumer")
public class Consumer82Controller {
    @Resource
    private RestTemplate restTemplate;

    @Value("${url.nacos-user-service}")
    private String URL;

    @GetMapping("/fallback/{id}")
    @SentinelResource(value = "fallback") //没有配置其他属性,只为sentinel控制台能够检测出
    public CommonResult fallback(@PathVariable Integer id) {
        CommonResult result = restTemplate.getForObject(URL + "/payment/" + id, CommonResult.class,id);
        if (id == 4) {
            throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }
        return result;
    }
}
1、启动服务消费者
2、接口地址:http://localhost:82/consumer/fallback/1
3、发现返回的端口号会交替出现(9003、9004),达到负载均衡的效果

三、Sentinel实现熔断与限流

4、当传入的参数大于等于4,就会让人看到Error页面不友好

三、Sentinel实现熔断与限流

2、只配置fallback

@RestController
@RequestMapping("consumer")
public class Consumer82Controller {
    @Resource
    private RestTemplate restTemplate;

    @Value("${url.nacos-user-service}")
    private String URL;

    @GetMapping("/fallback/{id}")
    @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
    public CommonResult fallback(@PathVariable Integer id) {
        CommonResult result = restTemplate.getForObject(URL + "/payment/" + 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  Integer id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult("444","兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
    }
}
1、重启服务消费者
2、接口地址:http://localhost:82/consumer/fallback/5
3、当程序运行过程中出现异常,全都由fallback的方法兜底

三、Sentinel实现熔断与限流
三、Sentinel实现熔断与限流

3、只配置blockHandler

@RestController
@RequestMapping("consumer")
public class Consumer82Controller {
    @Resource
    private RestTemplate restTemplate;

    @Value("${url.nacos-user-service}")
    private String URL;

    @GetMapping("/fallback/{id}")
    @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
    public CommonResult fallback(@PathVariable Integer id) {
        CommonResult result = restTemplate.getForObject(URL + "/payment/" + id, CommonResult.class,id);
        if (id == 4) {
            throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }
        return result;
    }
    
    /**
     * blockHandler是针对sentinel配置违规的兜底方法
     */
    public CommonResult blockHandler(@PathVariable  Integer id, BlockException blockException){
        Payment payment = new Payment(id,"null");
        return new CommonResult("445","blockHandler-sentinel限流,无此流水: blockException  " + blockException.getMessage(),payment);
    }
}
1、重启服务消费者
2、接口地址:http://localhost:82/consumer/fallback/5
3、新建一个降级规则

三、Sentinel实现熔断与限流

4、每秒一次就报错Error页面,快速点击,发现服务被限流,不可用

三、Sentinel实现熔断与限流

3、fallback和blockHandler都配置

@RestController
@RequestMapping("consumer")
public class Consumer82Controller {
    @Resource
    private RestTemplate restTemplate;

    @Value("${url.nacos-user-service}")
    private String URL;

    @GetMapping("/fallback/{id}")
    @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler")
    public CommonResult fallback(@PathVariable Integer id) {
        CommonResult result = restTemplate.getForObject(URL + "/payment/" + 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  Integer id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult("444","兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
    }

    /**
     * blockHandler是针对sentinel配置违规的兜底方法
     */
    public CommonResult blockHandler(@PathVariable  Integer id, BlockException blockException){
        Payment payment = new Payment(id,"null");
        return new CommonResult("445","blockHandler-sentinel限流,无此流水: blockException  " + blockException.getMessage(),payment);
    }
}
1、重启服务消费者
2、新建一个降级规则

三、Sentinel实现熔断与限流

3、疯狂点击:http://localhost:82/consumer/fallback/1,出现限流原因是每秒通过的QPS大于1就降级了

三、Sentinel实现熔断与限流

4、http://localhost:82/consumer/fallback/4,一秒访问一次,发现会调用fallback对应的方法

三、Sentinel实现熔断与限流

5、http://localhost:82/consumer/fallback/4,快速,发现服务降级,调用blockHandler对应的方法

三、Sentinel实现熔断与限流

总结:若fallback和blockHandler都进行了配置,则被限流降级而抛出BlockException时只会进入blockHandler对应的兜底方法。

十、整合Feign

1、修改服务消费者

cloudalibaba-consumer-nacos-order82

1、pom添加Feign支持

<!--feign相关-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2、yml添加Feign配置

#对Feign的支持
feign:
  sentinel:
    enabled: true

3、Feign接口

通过使用@FeignClient注解调用Provider服务,新建一个feign包,用来存放feign接口
fallback参数:定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须是实现@FeignClient标记的接口
@FeignClient(value = "nacos-payment-provider",
        configuration = {ProviderFeignClient.ProviderFeignClientConfigure.class},
        fallback = ProviderFeignClientImpl.class)
public interface ProviderFeignClient {
    @GetMapping(value = "/payment/{id}")
    CommonResult payment(@PathVariable("id") Integer id);

    class ProviderFeignClientConfigure{
        //设置超时时间
        @Bean
        Request.Options options(){
            return new Request.Options(30000,60000);
        }
    }
}

Feign接口实现类

@Service
public class ProviderFeignClientImpl implements ProviderFeignClient {
    @Override
    public CommonResult payment(Integer id) {
        return new CommonResult("44444","服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}

4、新建一个controller类

@RestController
public class ProviderFeignController {
    @Resource
    private ProviderFeignClient providerFeignClient;

    @GetMapping(value = "/feign/payment/{id}")
    public CommonResult payment(@PathVariable("id") Integer id) {
        return providerFeignClient.payment(id);
    }
}

5、主启动类添加@EnableFeignClients注解,启动feign功能

6、测试

1、正常访问接口:http://localhost:82/feign/payment/1,返回数据中端口号交替出现,说明实现负载均衡

三、Sentinel实现熔断与限流

2、当关闭一个服务提供者,或者两个都关闭,调用远程接口失败或超时时,会调用对应接口的容错逻辑,也就是feign接口实现类中的对应方法

三、Sentinel实现熔断与限流

2、熔断框架比较

三、Sentinel实现熔断与限流

十一、规则持久化

1、概述

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

2、实现方案

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

3、实现步骤

1、修改cloudalibaba-sentinel-service8401

2、添加依赖

<!--引入sentinel使用nacos存储规则的依赖-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

3、yml添加Nacos数据源配置

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        # server-addr: 192.168.241.135:8848/ #配置Nacos地址
        # 由于nacos配置成集群了,因此需要换成nginx监听的1111端口
        server-addr: 192.168.241.135:1111
    sentinel:
      transport:
        # sentinel 控制台地址
        dashboard: localhost:8080
        # 默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口
        port: 8720
      eager: true # 取消延迟加载(默认是延迟加载的)
      datasource:
        ds1:
          nacos:
          	# 配置nacos地址
            server-addr: 192.168.241.135:1111
            dataid: ${spring.application.name}
            groupid: DEFAULT_GROUP
            data-type: json
            rule-type: flow

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

4、添加Nacos配置规则

三、Sentinel实现熔断与限流

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

5、测试

1、启动8401服务后刷新sentinel,发现流控规则有了

三、Sentinel实现熔断与限流

2、快速访问接口http://localhost:8401/byResource,发现报默认的错误

三、Sentinel实现熔断与限流

3、停止8401服务,发现sentinel中没有流控规则了

三、Sentinel实现熔断与限流

4、再次启动服务,发现流控规则又出现了,说明实现了规则持久化