【SpringCloud Alibaba笔记】(2)Sentinel实现熔断与限流

发布时间:2024年01月07日

Sentinel

概述

官网:https://github.com/alibaba/Sentinel
中文文档:https://sentinelguard.io/zh-cn/docs/introduction.html

类似Hystrix,以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性

Hystrix与Sentinel区别

  • 需要我们程序员自己手工搭建监控平台
    没有一套web界面可以给我们进行更加细粒度化得配置流控、速率控制、服务熔断、服务降级…
  • 单独一个组件,可以独立出来。
    直接界面化的细粒度统一配置。

下载与安装

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

下载的是一个jar包

运行前提:8080端口不被占用,有java8环境

启动命令:java -jar sentinel-dashboard-1.7.0.jar

启动完成后访问http://localhost:8080/#/login,账号密码均为sentinel

在这里插入图片描述

Sentinel特性
在这里插入图片描述
Sentinel 分为两个部分:

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

初始化演示工程

先启动nacos

新建cloudalibaba-sentinel-service8401模块

pom文件

	<artifactId>cloudalibaba-sentinel-service8401</artifactId>
    <dependencies>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.mzr.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</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>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件+actuator -->
        <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>

yml

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.254.130:80 #Nacos服务注册中心地址,使用集群地址(可以看我上一章关于nacos笔记)
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719

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

主启动类

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

controller

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

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

启动微服务

访问http://localhost:8080/ 我们发现sentinel里空空如也,这是为什么呢?

在这里插入图片描述

因为sentinel使用的是懒加载机制,我们只需执行一次访问http://localhost:8401/testA即可

此时,sentinel正在监控微服务8401

在这里插入图片描述

流控规则

在这里插入图片描述

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

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

  • 阈值类型单机阈值:

    QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流。

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

  • 是否集群:不需要集群

  • 流控模式

    直接:api达到限流条件时,直接限流

    关联:当关联的资源达到阈值时,就限流自己

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

  • 流控效果

    快速失败:直接失败,抛异常

    Warm up:根据codeFactor (冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值

    排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效

流程模式

直接模式(默认)

演示:表示/请求路径testA 1秒钟内查询1次就是OK,若超过次数1,就直接快速失败,报默认错误

在这里插入图片描述

当我们快速连点时,就会报错

在这里插入图片描述
线程数与QPS大致相同,设置为1时表示在一个线程正在处理时,有其他线程进来会返回Blocked by Sentienl(flow limiting),之后全部使用QPS演示

直接调用默认报错信息,技术方面OK,but是否应该有我们自己的后续处理(我们自定义报错信息)?

关联模式

当与A关联的资源B达到阀值后,就限流A自己(支付微服务达到阈值,订单模块限流,避免连坐)

演示:设置/请求路径testB QPS为1,模拟并发密集访问/testB一定会导致B达到阈值,观察A的情况

在这里插入图片描述

postman模拟并发密集访问/testB(20个线程间隔0.3s访问一次)

在这里插入图片描述

在模拟期间我们访问 http://localhost:8401/testA 会发现A挂了,这就是关联模式

在这里插入图片描述

链路模式

只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。

演示:有两条链路 /testA -> /common、/testB -> /common

如果从 /testA 进入到 /common的请求阈值超过了1次/s,则对/testA 进行限流

在这里插入图片描述

流控效果

直接效果->快速失败(默认的流控处理)

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

源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

预热效果

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

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

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

在这里插入图片描述

源码所在类:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

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

演示:阀值为10+预热时长设置5秒。
系统初始化的阀值为10/3约等于3,即阀值刚开始为3,然后过了5秒后阀值才慢慢升高恢复到10

在这里插入图片描述

排队等待效果

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

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

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

演示:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。

在这里插入图片描述

降级规则

官网

在这里插入图片描述

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

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

注意Sentinel默认统计的RT上限是4900 ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项
-Dcsp.sentinel.statistic.max.rt=xxx来配置。

  • 异常比列(秒级)

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

异常比率的阈值范围是[0.0,1.0],代表0% -100%。

  • 异常数(分钟级)

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

Sentinel的断路器是没有半开状态的

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

RT规则

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

在这里插入图片描述

在这里插入图片描述

永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒(程序设计为1s)处理完本次任务,如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了

在这里插入图片描述

后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK

在这里插入图片描述

异常比例规则

@GetMapping("/testD")
    public String testD()
    {
        log.info("testD 异常比例");
        int age = 10/0;
        return "------testD";
    }

在这里插入图片描述
在这里插入图片描述
启动测试,访问http://localhost:8401/testD

单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次
在这里插入图片描述

开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件(异常比例0.2)

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

在这里插入图片描述

异常数规则

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

在这里插入图片描述
启动测试,访问http://localhost:8401/testE

第一次访问绝对报错(int age = 10/0),因为除数不能为零我们看到error窗口

在这里插入图片描述

但是达到5次报错后,进入熔断后降级

在这里插入图片描述

热点key限流

在这里插入图片描述

源码类:com.alibaba.csp.sentinel.slots.block.BlockException

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

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

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

兜底方法 分为系统默认和客户自定义两种
之前的case,限流出问题后,都是用sentinel系统默认的提示:Blocked bySentinel (flow limiting)
我们能不能自定义呢?
类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
@SentinelResource可以帮我们实现,这里先引入使用以下,之后会详细讲解
这里声明一下:@SentinelResource与热点规则没有必然联系,这里使用@SentinelResource只是为了演示自定义返回限流提示

在8401模块controller添加方法

	@GetMapping("/testHotKey")
	//@SentinelResource的value值随意,唯一即可,一般为了编码统一规范与请求地址保持一致
	//blockHandler = "deal_testHotKey" 如果违背了Sentinel配置规则,则调用deal_testHotKey方法
    @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
    public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                             @RequestParam(value = "p2",required = false) String p2)
    {
        //int age = 10/0;
        return "------testHotKey";
    }
    //方法头基本与@SentinelResource配置方法一致,只是入参必须要加BlockException exception 
    public String deal_testHotKey (String p1, String p2, BlockException exception)
    {
        return "------deal_testHotKey,o(╥﹏╥)o";  //sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
    }

演示:方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理,并返回自定义结果

资源名:这里可以传@SentinelResource的value

参数索引:传入的热点参数的索引(从0开始)

在这里插入图片描述

启动测试

一秒访问一次 http://localhost:8401/testHotKey?p1=0 返回值正常,当我们快速访问两次会发现返回o(╥﹏╥)o,发现服务已经降级

在这里插入图片描述
此时我们去掉@SentinelResource中的blockHandler = “deal_testHotKey”,再次访问

异常打到了前台用户界面看到,不友好,所以建议一定要配置上blockHandler

在这里插入图片描述

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

偶尔也会有特殊情况,比如我们希望参数p1值为5时,它的阈值可以达到200,值为1时阈值为依然为1

这时我们应该如何配置呢?这里需要用到高级配置,如下

整体配置含义:参数下标是第0个也就是p1时,当QPS超过1秒1次点击后马上被限流,当p1值为5时,阈值则变为200,其他值阈值都为1

在这里插入图片描述

启动测试

p1=1时,我们快速连续访问两次便会触发限流

在这里插入图片描述
当p1=5时,我们手动快速点击多次(不超过200)都正常返回,达到我们的预期

在这里插入图片描述
此时,我们突然手贱,在controller层添加了程序异常(int age = 10/0),如下

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

启动测试

发现异常打到了前台用户界面,怎么回事??

在这里插入图片描述

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

RuntimeException是java运行时报出的,运行时异常RunTimeException,@SentinelResource不管

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

系统规则

官网

我们上边学习的限流都是方法级别的,只对某个方法有效,如果我们想对整个系统进行限流

便需要使用系统规则,从整体维度对应用入口流量进行控制

在这里插入图片描述
系统规则支持以下的模式:

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

系统规则使用的不多,颗粒度太大

使用入口QPS演示

当QPS超过1秒1次时,触发系统服务限流

在这里插入图片描述

启动测试

我们分别单点访问/testA、/testB方法是正常返回结果的

当我们快速连续访问/testA、/testB,AB均已限流

得出结论:我们配置的系统规则对所有方法都有效

在这里插入图片描述
在这里插入图片描述

@SentinelResource详解

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

改造8401微服务

新增RateLimitController

@SentinelResource包含blockHandler 属性

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

配置流控规则

在这里插入图片描述

启动测试,访问http://localhost:8401/byResource,一秒一次正常返回

在这里插入图片描述

快速多次访问,返回了自己定义的限流处理信息,限流发生

在这里插入图片描述

@SentinelResource不包含blockHandler 属性

如果我们此时去掉@SentinelResource(value = “byResource”,blockHandler = “handleException”)中的blockHandler

再次快速多次访问 http://localhost:8401/byResource,会发现出现了error页面 很不友好对吧!!这个我们在学习热点限流时已经接触过了

在这里插入图片描述

此时重新启动微服务8401看看

Sentinel控制台,流控规则消失了? ? ? ?

在这里插入图片描述

这个问题后续会讲解

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

继续使用该方法

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

配置流控规则

在这里插入图片描述

启动测试,访问一次 http://localhost:8401/byResource

在这里插入图片描述

快速多次访问,会发现返回默认信息而不是我们自定义的信息

在这里插入图片描述

上面两种配置的区别及兜底方案面临的问题

区别

按url限流: 配置资源名为访问路径,使用url限流时触发流控规则会报默认错误。

按资源限流: 如果@SentinelResource
指定了blockHandler 会执行自定义的方法 如上handleException()
没指定blockHandler 会将异常打到了前台用户界面看到,很不友好!!
实际开发中超过阈值时,我们要使用@SentinelResource注释一定要定义一些兜底的方法。

面临的问题

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

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

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

全局统—的处理方法没有体现。

客户自定义限流处理逻辑

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

创建自定义限流处理类com.mzr.springcloud.CustomerBlockHandler

public class CustomerBlockHandler
{
    public static CommonResult handlerException(BlockException exception)
    {
        return new CommonResult(4444,"按客戶自定义,global handlerException----1");
    }
    public static CommonResult handlerException2(BlockException exception)
    {
        return new CommonResult(4444,"按客戶自定义,global handlerException----2");
    }
}

RateLimitController

@GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "customerBlockHandler",
    		//限流时分门别类的选择调用哪个类中的哪个异常方法
    		//例如CustomerBlockHandler中的handlerException2方法
            blockHandlerClass = CustomerBlockHandler.class,
            blockHandler = "handlerException2")
    public CommonResult customerBlockHandler() {
        return new CommonResult(200, "按客戶自定义", new Payment(2020L, "serial003"));
    }

配置流控规则

在这里插入图片描述

启动测试,访问一次 http://localhost:8401/rateLimit/customerBlockHandler

在这里插入图片描述

多次快速访问,可以看到返回的是我们自定义的方法

在这里插入图片描述

测试后我们自定义的出来了进一步说明

在这里插入图片描述

@SentinelResource更多注解属性说明

在这里插入图片描述

服务熔断功能

sentinel整合ribbon+openFeign+fallback

Ribbon环境

新建cloudalibaba-provider-payment9003/9004

9003/9004配置基本相同,记得修改端口号

pom

<dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.mzr.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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>
    </dependencies>

yml

server:
  port: 9003

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.254.138:8848 #配置Nacos地址

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

主启动类

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

controller

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

新建消费者 cloudalibaba-consumer-nacos-order84

pom

	<dependencies>
        <!--SpringCloud openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </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>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.mzr.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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>
    </dependencies>

yml

server:
  port: 84

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

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

主启动类

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

既然使用ribbon,肯定少不了我们的RestTemplate 配置

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

controller

@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发现9004/9003交替出现,负载均衡访问

在这里插入图片描述

服务熔断无配置

看上诉代码得知上述代码中 @SentinelResource(value = “fallback”) 没有任何配置

此时我们访问http://localhost:84/consumer/fallback/4和http://localhost:84/consumer/fallback/5发现返回都是java程序运行异常的error页面,对于用户很不友好

在这里插入图片描述

服务熔断只配置fallback

修改注解配置为@SentinelResource(value = “fallback”,fallback = “handlerFallback”)并添加兜底方法

	@RequestMapping("/consumer/fallback/{id}")
    @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 ("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);
    }

重新启动,再次访问http://localhost:84/consumer/fallback/4、和http://localhost:84/consumer/fallback/5

可以发现当前返回已经相对友好些

在这里插入图片描述

服务熔断只配置blockHandler

修改注解配置为@SentinelResource(value = “fallback”,blockHandler = “blockHandler”)并添加sentinel配置

@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);
    }

在这里插入图片描述

此时访问http://localhost:84/consumer/fallback/4

访问第1次,此时还没有达到我们sentinel降级配置要求,由于程序异常并且没有fallback配置出现不友好的error页面

在这里插入图片描述

快速连续访问多次,此时达到sentinel降级配置触发降级

在这里插入图片描述

服务熔断同时配置fallback及blockHandler

修改注解配置为@SentinelResource(value = “fallback”,fallback = “handlerFallback”,blockHandler = “blockHandler”),并新增sentinel降级配置

	@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,此时并没有达到我们sentinel降级配置要求,但配置了fallback,所以程序异常会调用兜底方法返回错误信息

在这里插入图片描述

快速连续访问2次,此时达到我们sentinel降级配置要求,但同时程序异常会触发调用兜底方法,此时会返回什么呢??很明显返回的是限流信息

在这里插入图片描述

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

服务熔断配置exceptionsToIgnore

修改注解配置为@SentinelResource(value = “fallback”,fallback = “handlerFallback”,blockHandler = “blockHandler”,exceptionsToIgnore = {IllegalArgumentException.class})

其他配置与同时配置fallback及blockHandler案例相同

重新启动,访问一次http://localhost:84/consumer/fallback/4

在这里插入图片描述

发现触发程序异常但没有调用我们配置的兜底方法,why???

在这里插入图片描述

Feign环境

修改84微服务,Feign组件一般是在消费侧,使用84消费者调用提供者9003

pom增加openFeign依赖

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

yml文件激活Sentinel对Feign的支持

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

主启动类添加注解@EnableFeignClients

带@FeignClient注解的业务接口

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService
{
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}

fallback = PaymentFallbackService.class

@Component
public class PaymentFallbackService implements PaymentService
{
    @Override
    public CommonResult<Payment> paymentSQL(Long id)
    {
        return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}

Controller

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

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

启动测试84调用9003,访问 http://localhost:84/consumer/paymentSQL/1,服务当前正常运行

在这里插入图片描述

此时故意关闭9003微服务提供者,看84消费侧是否会自动降级调用兜底方法,还是会被自动耗死

答案是会自动降级调用兜底方法

在这里插入图片描述

注意注意!!
此案例只是演示了Sentinel对openFeign的支持,可以与Hystrix作一下对比看有什么区别,而关于sentinel本身的降级配置在此处并没有演示,大家可以自行配置尝试一下

规则持久化

还记得学习@SentinelResource详解时遇到的一个问题吗

一旦我们应用重启,Sentinel控制台配置的规则消失了? ? ? ?

生产环境需要将配置规则进行持久化

如何持久化

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

这里使用8401进行演示

RateLimitController新增方法byUrl

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

此时sentinel没有配置,我们正常访问 http://localhost:8401/rateLimit/byUrl

在这里插入图片描述

配置sentinel流控规则

在这里插入图片描述

此时快速连续访问http://localhost:8401/rateLimit/byUrl,会发现此时流控规则已经生效

在这里插入图片描述

此时我们将微服务重新启动,发现sentinel控制台配置信息已经没有了

如何持久化呢??

修改8401微服务

pom添加依赖

<!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>

yml添加配置信息

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址,使用集群地址(可以看我上一章关于nacos笔记)
        server-addr: 192.168.254.138:8848
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: 192.168.254.138:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

添加Nacos业务规则配置

在这里插入图片描述

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

启动8401后刷新sentinel发现业务规则有了(没有就先访问http://localhost:8401/rateLimit/byUrl即可)

在这里插入图片描述

快速多次访问测试接口,发现流控规则已经生效

在这里插入图片描述

停止8401再看sentinel,发现sentinel控制台流控规则消失了

在这里插入图片描述

重新启动8401先访问http://localhost:8401/rateLimit/byUrl 再刷新sentinel页面

在这里插入图片描述

重新配置出现了,持久化验证通过~

文章来源:https://blog.csdn.net/weixin_45734647/article/details/135329902
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。