服务熔断与降级(Sentinel)

1、概述

官方Git仓库

中文文档

①、什么是Sentinel

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

回顾以前的Hystrix,我们做到了服务熔断,服务降级,但是Hystrix没有一套Web界面可以让我们更加细粒化的配置,SentinelNacos一样,不再需要我们手动的去创建某个微服务模块来专门实现对应功能,直接用一套Web界面供我们进行细粒化的统一配置,管理。

②、Windows下载安装

下载地址

image-20210221222814268

image-20210221222831350

Sentinel分为两个部分:

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

开始运行,保证java8环境正常,8080端口不被占用(注意与Tomcat区分开)

java -jar sentinel-dashboard-1.8.1.jar

image-20210221223822406

直接访问localhost:8080管理界面,账号密码都是sentinel

image-20210221224219319

③、Docker安装

docker pull bladex/sentinel-dashboard
#此docker镜像默认端口不是8080了,而是8858
docker run --name sentinel -d -p 8858:8858 bladex/sentinel-dashboard

浏览器访问,别忘记安全组

image-20210222094938872

Sentinel案例我没有用Docker了,因为我发现Sentinel能找到服务,但是无法实时监控,也就是实时监控列表为空,初步猜测是我连接的移动宽带的wifi,是一个内网环境,我能找到SentinelSentinel找不到服务,所以案例以本地Sentinel

2、工程案例

①、建Module

image-20210222093353029

②、POM

<?xml version="1.0" encoding="UTF-8"?>
<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>SpringCloudDemo</artifactId>
        <groupId>com.phz.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>SentinelService8401</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.phz.springcloud</groupId>
            <artifactId>CloudAPI</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- SpringCloud ailibaba nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- SpringCloud ailibaba sentinel-->
        <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.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>
</project>

③、YML

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinal-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: 39.105.43.3:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址,如果这里使用了docker的Sentinel,默认端口不再是8080,而是8858
        dashboard: localhost:8080
        # 和Sentinel进行数据交互的默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口
        # 指定应用与Sentinel控制台交互的端口,应用程序本地会发起一个占用该端口的HttpServer
        port: 8719

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

④、主启动

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/22 9:36
 * @projectName SpringCloudDemo
 * @className SentinelMain8401.java
 * @description TODO
 */
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelMain8401 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(SentinelMain8401.class, args);
    }
}

⑤、业务类

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/22 9:40
 * @projectName SpringCloudDemo
 * @className FlowLimitController.java
 * @description TODO
 */
@RestController
@Slf4j
public class FlowLimitController {
    
    
    @GetMapping("/testA")
    public String testA() {
    
    
        return "----testA";
    }

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

⑥、测试

开启Sentinel

java -jar sentinel-dashboard-1.8.1.jar

image-20210223095749699

开启服务

image-20210222095123248

image-20210222095352786

Sentinel由于采用了懒加载机制,监控的服务需要被访问才会被加载,所以先执行业务方法,再刷新Sentinel面板

image-20210222095726231

image-20210223100015072

3、流控规则

打开Sentinel面板,可以新增流控规则

image-20210223102617277

也可以打开簇点链路指定某资源名新增流控,如图

image-20210223102848008

①、流控

Ⅰ、相关概念

  • 资源名:唯一名称。默认请求路径

  • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default (不区分来源)

  • 阈值类型/单机阈值

    • QPS(每秒钟的请求数量):当调用该apiQPS达到阈值的时候,进行限流。
    • 线程数:当调用该api的线程数达到阈值的时候,进行限流
  • 是否集群:不需要集群

  • 流控模式:

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

    • 快速失败:直接失败,抛异常
    • Warm up:根据codeFactor (冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值

Ⅱ、QPS直接快速失败

testA配置一个流控规则,表示每秒请求数量超过一个直接失败,也就是疫苗只能通过一此A请求

image-20210223103554863

连续快速点击,效果如下,报出Block By Sentinel(flow limiting)提示

QQ录屏20210223103818

Ⅲ、线程数直接快速失败

修改流控规则,阈值类型修改为线程数,这个线程数是什么意思呢,类比前一个QPS,他是每一秒钟只能有指定的请求数量才能访问,只要满足要求,就执行请求,如果线程数量指定为1,那么就意味着,这个资源同时只能被一个线程所占用,其他的请求来了,但是前一个请求还没有结束,便不能获取资源。

image-20210223104310028

testA方法添加一个延时语句

try{
    
    
    TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
    
    
    e.printStackTrace();
}

执行访问

QQ录屏20210223111721_1

Ⅳ、关联

关联也就是其他的资源出问题了,限流自己,比如在微服务中,支付服务出现问题,就限流下订单服务。

image-20210223112546432

使用PostMan模拟密集访问testB,记得去掉上面的延时语句

image-20210223113042597

image-20210223113132689

image-20210223113246279

image-20210223113321321

然后点击运行,访问testA

image-20210223113619929

Ⅴ、预热

修改testB的流控规则

image-20210223140909669

访问

QQ录屏20210223141027

公式:阈值除以coldFactor(默认值为3),经过预热时长后才会阈值,即请求QPS阈值/3开始,经预热时长逐渐升到设定的QPS阈值

Ⅵ、排队等待

匀速排队,让请求以均匀的速度通过,阈值类型必须为QPS,否则无效,修改testB流控规则,表示一秒一次请求,超过就排队,等待的超时时间为2000ms

image-20210223142649857

testB加一行打印语句,postman每一秒访问两次

image-20210223143901423

观察控制台,可以看到,10次请求并不是按照预先设定的那样一秒两次,而是一秒一次,当一秒钟出现了两次请求时,在处理了其中一个请求后,另一个并不会直接报错销毁,而是进入排队等待。

image-20210223143925756

②、降级

Ⅰ、概念

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

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

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

  • 异常数(分钟级)

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

Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)

Ⅱ、降级策略

我们通常用以下几种方式来衡量资源是否处于稳定的状态:

  • 平均响应时间(DEGRADE_GRADE_RT):当1s内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值( count,以ms为单位),那么在接下的时间窗口(DegradeRule中的 timewindow,以s为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意Sentinel默认统计的RT上限是4900 ms,超出此阈值的都会算作4900 ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置。
  • 异常比例(DEGRADE_GRADE_EXCEPTION_RATIo ):当资源的每秒请求量>=5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule中的 timewindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[e.0,1.0],代表0%- 100%
  • 异常数(DEGRADE_GRADE_EXCEPTION_COUNT ):当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timewindow小于60s,则结束熔断状态后仍可能再进入熔断状态。

image-20210224152619906

Ⅲ、RT

取消注释testA的延时语句,一次请求睡一秒,新增降级规则

image-20210224150624198

然后开启Jmeter压力测试,一秒十个线程访问

image-20210224151038484

再次访问testA,就访问不了了

image-20210224151218362

由于设置的熔断时长为60s,当线程组执行完毕后,要等待60s后再次访问才能正常

image-20210224151400779

Ⅳ、异常比例

注释testA延时语句,新增一条异常语句,如10/0,然后修改降级规则。

image-20210224152001571

Jmeter1S内同时发送10条请求,然后执行访问,不再是以往的页面显示一个500的错误,而是一个Sentinel的自定义提示,前提一定是要注意一个地方,那就是一秒内请求数量一定要超过5个,不然还是会报500

image-20210224152117600

等待线程执行结束,然后等5s(熔断时长),再次访问

image-20210224152236794

Ⅴ、异常数

修改降级规则

image-20210224152744125

Jmeter继续发请求

image-20210224152816707

等待线程结束且5s

image-20210224152919842

③、热点规则

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

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

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。

Ⅰ、常规演示

新增hostKey方法,其中一条@SentinelResource注解混个眼熟,后面会单独介绍,blockHandler属性必须指定,否则Sentinel面板如果设置了热点规则,这里又不配,将直接返回一个500的对于用户不友好的错误页面,而且它只帮我们处理Sentinel添加的配置的违规情况,如果是程序本身的错误也就是RuntimeException,是不会进入兜底方法,这个后面也会说

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey")//注意这个value不一定和mapping一致,只要是唯一即可,在Sentinel面板添加热点规则的时候应该指定这个值,注意没有斜杠,blockHandler对比Hystrix的HystrixCommand注解的fallbackMethod属性,指定了兜底方法。
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                         @RequestParam(value = "p2", required = false) String p2) {
    
    //两个参数都指定可选
    return "----testHotKey";
}

//兜底方法
public String deal_testHotKey(String p1, String p2, BlockException exception) {
    
    
    return "----deal_testHotKey, o(╥﹏╥)o"; // sentinel的默认提示都是: Blocked by Sentinel (flow limiting)
}

新增热点规则,参数索引0表示当第一个参数也就是p1所携带的值在相同的情况下一秒只能有一个QPS,否则就会进入自定义兜底方法

image-20210224155429109

狂发一条带有p1参数的请求,可以看到提示信息就是我们自己定义的兜底方法

image-20210224155552629

②、参数例外项

我们前面配置了p1的热点规则,但是我们希望对于p1的某一个值比如5的时候,他的阈值能够到200而不是1呢,所以就可以配置参数例外项来达到需求

image-20210224160731066

再次测试就会发现,狂发携带参数p1=5的请求,一切正常了。

④、系统规则

①、相关概念

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

image-20210224162006899

不做测试,有兴趣自己试试,这个一般用的不多

4、SentinelResource

便于演示,新建一个Controller

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/24 16:27
 * @projectName SpringCloudDemo
 * @className RateLimitController.java
 * @description TODO
 */
@RestController
@Slf4j
public class RateLimitController {
    
    
    @GetMapping("/byResource")
    @SentinelResource(value = "byResource", blockHandler = "handleException")
    public CommonResult byResource() {
    
    
        return new CommonResult(200, "按照资源名称限流测试", new Payment(2020L, "serial001"));
    }

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

①、资源名称限流

image-20210224163356730

这个上面已经说了,不在赘述

image-20210224163626142

②、URL限流

添加新的方法

@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")//没有再指定兜底方法了
public CommonResult byUrl() {
    
    
    return new CommonResult(200, "按照byUrl限流测试", new Payment(2020L, "serial002"));
}

新增流控规则

image-20210224164018921

QPS超过1便会进入Sentinel给我们默认的兜底方法

③、目前存在的问题

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

又回到了Hystrix的时候出现的问题了

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

Ⅰ、创建CustomerBlockHandler类

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/24 16:47
 * @projectName SpringCloudDemo
 * @className CustomerBlockHandler.java
 * @description TODO
 */
public class CustomerBlockHandler {
    
    

    public static CommonResult handlerException1(BlockException exception) {
    
    
        return new CommonResult(444, "按照客户自定义的Glogal 全局异常处理 ---- 1", new Payment(2020L, "serial003"));
    }

    public static CommonResult handlerException2(BlockException exception) {
    
    
        return new CommonResult(444, "按照客户自定义的Glogal 全局异常处理 ---- 2", new Payment(2020L, "serial003"));
    }
}

Ⅱ、添加Controller

//CustomerBlockHandler
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
        blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")//可以看出blockHandler可以指定自定义blockHandler中的指定兜底方法
public CommonResult customerBlockHandler() {
    
    
    return new CommonResult(200, "按照客户自定义限流测试", new Payment(2020L, "serial003"));
}

测试一下,可以看到进入了我们自己定义的兜底方法了

image-20210224165206068

⑤、@SentinelResource属性汇总

属性名 是否必填 说明
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的异常

以上提到的方法接下来会用到一部分

5、整合Ribbon

①、服务提供者

Ⅰ、建Module

新建两个Module名称为Sentinel-provider-payment9003/9004

image-20210224172117278

Ⅱ、POM

<?xml version="1.0" encoding="UTF-8"?>
<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>SpringCloudDemo</artifactId>
        <groupId>com.phz.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Sentinel-provider-payment9004</artifactId>


    <dependencies>
        <!-- SpringCloud ailibaba nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- SpringCloud ailibaba sentinel-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.phz.springcloud</groupId>
            <artifactId>CloudAPI</artifactId>
            <version>1.0-SNAPSHOT</version>
        </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.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>
</project>

Ⅲ、YML

server:
  port: 9003

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

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

Ⅳ、主启动

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/24 17:27
 * @projectName SpringCloudDemo
 * @className PaymentMain9003.java
 * @description TODO
 */
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(PaymentMain9003.class, args);
    }
}

Ⅴ、业务类

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/24 17:24
 * @projectName SpringCloudDemo
 * @className PaymentController.java
 * @description TODO
 */
@RestController
public class PaymentController {
    
    

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

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

    //模拟service层调用数据库
    static {
    
    
        map.put(1L, new Payment(1L, "1111"));
        map.put(1L, new Payment(2L, "2222"));
        map.put(1L, new Payment(3L, "3333"));
    }


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

Ⅵ、测试

image-20210224173118002

image-20210224173127626

②、服务消费者

Ⅰ、建Module

image-20210224173224878

Ⅱ、POM

<?xml version="1.0" encoding="UTF-8"?>
<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>SpringCloudDemo</artifactId>
        <groupId>com.phz.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Sentinel-consumer-nacos-order8004</artifactId>

    <dependencies>
        <!-- SpringCloud ailibaba nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- SpringCloud ailibaba sentinel-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.phz.springcloud</groupId>
            <artifactId>CloudAPI</artifactId>
            <version>1.0-SNAPSHOT</version>
        </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.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>
</project>

Ⅲ、YML

server:
  port: 8004

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

#消费者将去访问的微服务名称
server-url:
  nacos-user-service: http://nacos-payment-provider

Ⅳ、主启动

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/24 17:34
 * @projectName SpringCloudDemo
 * @className OrderMain8004.java
 * @description TODO
 */
@EnableDiscoveryClient
@SpringBootApplication
public class OrderMain8004 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(OrderMain8004.class, args);
    }
}

Ⅴ、业务类

ApplicationContextConfig

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/24 17:36
 * @projectName SpringCloudDemo
 * @className ApplicationContextConfig.java
 * @description TODO
 */
@Configuration
public class ApplicationContextConfig {
    
    

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

Controller

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/24 17:37
 * @projectName SpringCloudDemo
 * @className CircleBreakerController.java
 * @description TODO
 */
@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("IllegalArgument ,非法参数异常...");
        } else if (result.getData() == null) {
    
    
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }
}

Ⅵ、测试

QQ录屏20210224174130

image-20210224174627021

③、配置一个fallback

@RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback")//不配置
@SentinelResource(value = "fallback", fallback = "handlerFallback")//仅配置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("IllegalArgument ,非法参数异常...");
    } else if (result.getData() == null) {
    
    
        throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
    }
    return result;
}

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

测试

image-20210224175233848

④、配置以个blockHandler

@RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback")//不配置
//    @SentinelResource(value = "fallback", fallback = "handlerFallback")//仅配置fallback
@SentinelResource(value = "fallback", 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("IllegalArgument ,非法参数异常...");
    } else if (result.getData() == null) {
    
    
        throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
    }

    return result;
}

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

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

新增降级规则

image-20210224175705286

单独访问错误方法直接500

image-20210224175739116

连续点击才能触发降级

image-20210224175757411

⑤、同时配置fallback和blockHandler

@RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback")//不配置
//    @SentinelResource(value = "fallback", fallback = "handlerFallback")//仅配置fallback
//    @SentinelResource(value = "fallback", blockHandler = "blockHandler")//仅配置blockHandler
@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("IllegalArgument ,非法参数异常...");
    } else if (result.getData() == null) {
    
    
        throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
    }

    return result;
}

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

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

测试

image-20210224180202893

也可以看出,优先级肯定是控制台的降级规则优先级更高,也就是只会进入blockHandler处理

⑥、exceptionsToIgnore

当再SentinelResource中加入以个exceptionsToIgnore属性,对于指定的异常类型进行忽略,本例也就是查找4的时候不会抛出IllegalArgumentException异常

@SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = IllegalArgumentException.class)

6、整合openFeign

feign一般用在消费端,这里修改8004

①、POM

新增依赖

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

②、YML

新增配置

feign:
  sentinel:
    # 开启feign对sentinel的支持
    enabled: true

③、主启动

添加以个注解

@EnableFeignClients

④、业务类

添加Feign接口类

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/24 18:12
 * @projectName SpringCloudDemo
 * @className PaymentService.java
 * @description TODO
 */
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
    
    
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}

添加Feign接口实现类

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/24 18:16
 * @projectName SpringCloudDemo
 * @className paymentFallbackService.java
 * @description TODO
 */
@Component
public class PaymentFallbackService implements PaymentService{
    
    
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
    
    
        return new CommonResult<>(444,"服务降级返回,---PaymentFallbackService",new Payment(id,"ErrorSerial"));
    }
}

controller新增方法

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

⑤、测试

image-20210224182405115

停掉90039004,测试通过

image-20210224182547565

7、持久化

①、问题提出

在我们前面配置各种限流和降级等等规则的时候,如果我们把服务重启了,对应的配置规则便消失了,我们能否将配置好的规则持久化呢?

②、解决办法

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

③、POM

POM文件新增以个依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

④、YML

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinal-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: 39.105.43.3:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址,如果这里使用了docker的Sentinel,默认端口不再是8080,而是8858
        dashboard: localhost:8080
        # 和Sentinel进行数据交互的默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口
        # 指定应用与Sentinel控制台交互的端口,应用程序本地会发起一个占用该端口的HttpServer
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: 39.105.43.3:8848  #nacos
            dataId: ${
    
    spring.application.name}
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

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

⑤、nacos新增配置

image-20210224183821900

JSON串为

[
    {
    
    
        "resource": "/rateLimit/byUrl",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]
  • resource:资源名称
  • limitApp:来源应用
  • grade:阈值类型,0表示线程数量,1表示QPS
  • count:单机阈值
  • strategy:流控模式,0表示直接,1表示关联,2表示链路
  • controlBehavior:流控效果,0表示快速失败,1表示warm Up2表示排队等待
  • clusterMode:是否集群

⑥、测试

重启8401,观察sentinel控制台

image-20210224184358790

image-20210224184513003

猜你喜欢

转载自blog.csdn.net/qq_43509535/article/details/114034635