Sentinel

cloud alibaba

Sentinel 实现熔断与限流

1、准备

1)、下载

中文官网

哪里下载

下载 sentinel-dashboard-1.7.1.jar

Sentinel 能干嘛:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EV5v2QGh-1655801130093)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620101631078.png)]

怎么玩?

Sentinel 分为两个部分:

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

安装:

  1. 前提:
    Java8 环境
    8080 端口不可被占用

  2. 运行命令:
    java -jar sentinel-dashboard-1.7.0.jar
    可以更改启动端口 java -jar sentinel-dashboard-1.7.0.jar --server.port=8333

  3. 访问 sentinel 管理界面
    http://localhost:8080
    账号、密码:sentinel

2)、初始化演示工程

启动 Nacos8848 成功
新建 module :sentinel-service8401

1 pom.xml

<parent>
    <artifactId>cloud</artifactId>
    <groupId>com</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>sentinel-service8401</artifactId>

<dependencies>
    <!-- SpringCloud ailibaba nacos  -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到 -->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
    <!-- SpringCloud ailibaba 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>
    <!--  actuator组件  -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2 配置文件

需要配置两个端口

dashboard 控制台的端口

port 用于数据传输的端口 默认8719

server:
  port: 8401

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

# 暴露所有端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

3 主启动类

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

4 接口

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

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

5 测试

  1. 启动 sentinel 8080
    java -jar sentinel-dashboard-1.7.0.jar

  2. 启动服务 8401

  3. 启动 8401微服务后查看sentienl控制台
    空空如也,啥都没有

Sentinel采用的 懒加载 说明:

必须要先执行 http://localhost:8401/testA、http://localhost:8401/testB
然后再查看 sentienl 控制台,才能发现 sentinel8080 正在监控微服务8401

2、实时监控(Endpoint 支持)

在这里插入图片描述

在使用 Endpoint 特性之前需要在 Maven 中添加 spring-boot-starter-actuator 依赖,并在配置中允许 Endpoints 的访问。

  • Spring Boot 1.x 中添加配置 management.security.enabled=false。暴露的 endpoint 路径为 /sentinel
  • Spring Boot 2.x 中添加配置 management.endpoints.web.exposure.include=*。暴露的 endpoint 路径为 /actuator/sentinel

需要添加依赖

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

并添加配置

management.endpoints.web.exposure.include=*

3、流控规则

在控制台调整参数,默认所有的流控设置保存在内存中,服务重启失效

Sentinel 支持注册中心持久化,注册中心支持数据库持久化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tO7F4LdN-1655801130095)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620103344051.png)]

解释说明:

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

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

  • 阈值类型 / 单机阈值

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

  • 流控模式

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

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

    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)

      api 级别的针对来源

  • 流控效果

    • 快速失败:直接失败,抛异常
    • Warm Up:根据 codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
      慢启动
    • 排队等待:均速排队,让请求以均速通过,阀值类型必须是QPS否则无效

1)、流控模式

1 直接(默认)

系统默认:直接 --> 快速失败

分 QPS、线程数阈值限制,下面演示是 QPS 阈值限制

下面配置表示 1 秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fjIrimrj-1655801130095)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620151824637.png)]

当快速点击访问 http://localhost:8401/testA,页面提示错误:Blocked by Sentinel (flow limiting)

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

2 关联

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

例如:当与A关联的资源 B 达到阀值后,就限流A自己。B惹事,A挂了。

比如支付资源达到阈值,限制下订单资源

示例:

配置 A :设置效果当关联资源 /testB 的qps阀值超过1时,就限流 /testA 的Rest访问地址,当关联资源到阈值后限制配置好的资源名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-btRbq2Us-1655801130096)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620153029250.png)]

测试: postman模拟并发密集访问testB

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SGrqCg3V-1655801130097)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620153310759.png)]

步骤:

访问testB成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lsrUsc3U-1655801130097)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620153526356.png)]

postman里新建多线程集合组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r8HMg3Ml-1655801130098)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620153613631.png)]

将访问地址添加进新新线程组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ogs8tiHp-1655801130098)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620153506708.png)]

大批量线程高并发访问B,导致A失效了

在线程高并发访问B的时候,浏览器手动运行A,发现 testA 挂了:Blocked by Sentinel (flow limiting)

3 链路

多个请求调用了同一个微服务

只限制指定入口请求过来

来自入口 Entrance1 和 Entrance2 的请求都调用到了资源 NodeA,Sentinel 允许只根据某个入口的统计信息对资源限流

2)、流控效果

1 快速失败

系统默认:直接 --> 快速失败

直接失败,抛出异常

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

2 预热

公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值

官网

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

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

WarmUp配置:

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vSGJbWTV-1655801130099)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620154846470.png)]

多次点击 http://localhost:8401/testB ,刚开始不行,后续慢慢OK

应用场景:

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

3 排队等待

匀速排队,让请求以均匀的速度通过,严格控制请求通过的间隔时间,对应的是漏桶算法

阀值类型必须设成QPS,否则无效

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

匀速排队,阈值必须设置为QPS

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-37AiYKfe-1655801130099)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620155145871.png)]

官网

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8riQ2T3v-1655801130100)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620155442265.png)]

4、降级规则

官网

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hj4acf3B-1655801130100)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620155955087.png)]

1)、基本介绍

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

Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。

当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

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

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

2)、降级策略实战

注意异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex) 记录业务异常

1 RT / 慢调用比例 (SLOW_REQUEST_RATIO)

现在官网称为 慢调用比例(SLOW_REQUEST_RATIO)

是什么

  • 慢调用比例(SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目(一般是5),并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。

若最小请求数目为5,设置允许的慢调用 RT为200ms,熔断时长为1s

当1秒内同时请求10个,因为10>5,1s>200ms,所以会进行熔断,熔断时间为1s,即1s内不会接收任何请求

测试代码:

@RestController
@Slf4j
public class FlowLimitController {
    @GetMapping("/testD")
    public String testD() {    //暂停几秒钟线程
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("testD 测试RT");
        return "------testD";
    }
}

JMeter测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lB9t7094-1655801130101)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620161330667.png)]

结论:

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

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

2 异常比例

是什么

  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目(一般是5),并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

达到配置条件才会触发熔断保护

配置条件:请求数目大于5,并且 异常比例大于阈值

即如果关闭了 JMeter,但是代码有异常,满足了后面条件但是没有满足前面条件,访问时就会直接报错抛异常,没有保护机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f3UBzw6R-1655801130102)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620161943079.png)]

测试代码:

@RestController
@Slf4j
public class FlowLimitController {
    @GetMapping("/testD")
    public String testD() {
        log.info("testD 测试异常比例");
        
        int i = 10/0;
        
        return "------testD";
    }
}

配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nknwoKEE-1655801130102)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620162107957.png)]

结论:

按照上述配置,单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次; 开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了

3 异常数

是什么

  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

时间窗口一定要大于等于60秒,因为异常数是按照分钟统计的。如果小于 60 s,则可能熔断恢复之后再次进入熔断

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hULN4msJ-1655801130103)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620162816415.png)]

测试代码:

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

配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wk9wHJKm-1655801130103)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620162951673.png)]

结论:

http://localhost:8401/testD,第一次访问绝对报错,因为除数不能为零,我们看到error窗口

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

5、热点规则

1)、热点 key 限流是什么

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9E8C2dCY-1655801130103)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620164021157.png)]

官网

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

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

兜底方法 分为 系统默认 和 客户自定义 两种

之前的案例里面,限流出问题后,都是用sentinel系统默认的提示:Blocked by Sentinel (flow limiting)

我们能不能自定义?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?

从 HystrixCommand 到@SentinelResource

2)、代码

value 名称唯一,可自定义,但是注意,在Sentinel控制台里面 配置资源名 必须和@SentinelResource注解的value 属性值保持一致

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

3)、配置

限流模式只支持QPS模式,固定写死了。(这才叫热点)@SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。上面的抓图就是第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用dealHandler_testHotKey支持方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JfXYCVmO-1655801130104)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620165653063.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DaoRUFoU-1655801130104)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620165543862.png)]

测试:

http://localhost:8401/testHotKey?p2=abc 不会降级

http://localhost:8401/testHotKey?p1=abc&p2=33、http://localhost:8401/testHotKey?p1=5 都会降级

  • @SentinelResource(value = “testHotKey”)
    • 异常打到了前台用户界面看到,不友好
  • @SentinelResource(value = “testHotKey”, blockHandler = “dealHandler_testHotKey”)
    • 方法 testHotKey 里面第一个参数只要QPS超过每秒1次,马上降级处理
    • 降级处理不是抛异常,而是执行指定的兜底方法

4)、参数例外项

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

但是有特殊情况

特殊情况:(开通VIP通道)

我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样

例如当p1的值等于5时,它的阈值可以达到200

注意: 热点参数的注意点,参数必须是 基本类型 或者 String

配置:

添加按钮不能忘

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kuuN5sJF-1655801130105)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620170113857.png)]

测试:

http://localhost:8401/testHotKey?p1=5 当p1等于5的时候,阈值变为200

http://localhost:8401/testHotKey?p1=3 当p1不等于5的时候,阈值就是平常的1

前提条件: 热点参数的注意点,参数必须是 基本类型 或者 String

其他

当方法里面有异常时

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

此时访问会出现报错页面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OTodgUJt-1655801130105)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620170846191.png)]

@SentinelResource

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

RuntimeExceptionint

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

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

6、系统规则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ekczUGzn-1655801130105)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20220620171344868.png)]

官网

是什么:

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

7、@SentinelResource

1)、按资源名称进行限流 + 自定义返回

  • 启动Nacos成功
    • http://localhost:8848/nacos/#/login
  • 启动Sentinel成功
    • java -jar sentinel-dashboard-1.7.0.jar

新建 module :sentinel-service8401

1 项目配置,与文章开头一样

pom.xml

<parent>
    <artifactId>cloud</artifactId>
    <groupId>com</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>sentinel-service8401</artifactId>

<dependencies>
    <!-- nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- sentinel-datasource-nacos 后续做持久化用到 -->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
    <!-- sentinel -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</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>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

配置文件

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认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);
    }
}

实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable{
    private Long id;
    private String serial;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>{
    private Integer code;
    private String  message;
    private T       data;

    public CommonResult(Integer code,String message)
    {
        this(code,message,null);
    }
}

2 接口使用 @SentinelResource

@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 服务不可用");
    }
}

3 控制台配置流控规则

配置步骤

在这里插入图片描述

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

4 测试

1秒钟点击1下,OK

超过上述,疯狂点击,返回了自己定义的限流处理信息,限流发生
在这里插入图片描述

5 问题

此时关闭问服务8401看看

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

临时/持久?

2)、按照Url地址限流 + 自带返回

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息

1 修改业务类

@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 服务不可用");
    }*/

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

2 控制台配置流控规则

在这里插入图片描述

或者是在下面的位置添加流控规则,注意选择的是 Url 地址限流

在这里插入图片描述

注意:如果资源名使用 URL的话, blockHandler 不起作用,会出现流控默认错误和自定义错误交替显示的问题

3 测试

疯狂点击 http://localhost:8401/rateLimit/byUrl

结果会返回 Sentinel 自带的限流处理结果

3)、自定义限流处理返回

上面兜底方案面临的问题:

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

1 自定义限流处理类

创建CustomerBlockHandler类用于自定义限流处理逻辑
在这里插入图片描述

注意方法返回值需要和业务方法保持一致
注意 static 关键字
public class CustomerBlockHandler {
    public static CommonResult handleException(BlockException exception) {
        return new CommonResult(2020, "自定义的限流处理信息......CustomerBlockHandler");
    }
} 

注意:

  1. 注意兜底方法和原方法返回值要一致
  2. 注意 static 关键字
  3. 注意这里兜底方法和源方法参数列表要保持一致,除兜底可以多一个BlockException参数
    资源有的参数兜底方法必须要有!!!

2 修改业务类

@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 服务不可用");
    }

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


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

启动微服务后先调用一次

http://localhost:8401/rateLimit/customerBlockHandler

3 配置

Sentinel控制台配置
在这里插入图片描述

测试后我们自定义的出来了
在这里插入图片描述

进一步说明
在这里插入图片描述

4)、Sentinel-自定义流控响应

添加“com.atguigu.gulimall.seckill.config.SeckillSentinelConfig”类,代码如下:

/**
 * @Description: Sentinel-自定义流控响应
 * @Author: WangTianShun
 * @Date: 2020/12/17 10:55
 * @Version 1.0
 */
@Configuration
public class SeckillSentinelConfig implements BlockExceptionHandler {
    /**
     * 2.2.0以后的版本实现的是BlockExceptionHandler;以前的版本实现的是WebCallbackManager
     * @param httpServletRequest
     * @param httpServletResponse
     * @param e
     * @throws Exception
     */
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
        R error = R.error(BizCodeEnume.TOO_MANY_REQUESTS_EXCEPTION.getCode(), BizCodeEnume.TOO_MANY_REQUESTS_EXCEPTION.getMsg());
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json");
        httpServletResponse.getWriter().write(JSON.toJSONString(error));
    }
 
    /**
     * 因为版本冲突导致无法引入 WebCallbackManager
     */
//    public SeckillSentinelConfig() {
//        WebCallbackManager.setUrlBlockHandler((request, response, ex) -> {
//            R error = R.error(BizCodeEnum.TOO_MANY_REQUESTS_EXCEPTION.getCode(), BizCodeEnum.TOO_MANY_REQUESTS_EXCEPTION.getMsg());
//            response.setCharacterEncoding("UTF-8");
//            response.setContentType("application/json");
//            response.getWriter().write(JSON.toJSONString(error));
//        });
//    }
}

在这里插入图片描述

5)、注解其他属性说明

@SentinelResource 注解
注意:注解方式埋点不支持 private 方法

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

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT)
  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。
    blockHandler 函数访问范围需要是 public返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback: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 逻辑中,而是会原样抛出。

注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理。

特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出

8、服务熔断功能

sentinel 整合 ribbon + openFeign + fallback

因为此处换了电脑,所以现在开始的版本背景为:

Spring Cloud AlibabaSpring CloudSpring BootSentinel VersionNacos Version
2.2.6.RELEASESpring Cloud Hoxton.SR92.3.2.RELEASE1.8.11.4.2

父工程 pom

<!-- 统一管理jar包版本 -->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
        <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
    </properties>

    <!-- 子模块继承之后,提供作用:锁定版本+module不用写version和groupId -->
    <dependencyManagement>
        <dependencies>
            <!-- spring boot 2.3.2.RELEASE -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- spring cloud Hoxton.SR9 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR9</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- spring cloud alibaba 2.2.6.RELEASE -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring.boot.version}</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>${log4j.version}</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <optional>true</optional>
            </dependency>
        </dependencies>
    </dependencyManagement>

8.1、Ribbon 系列

启动 nacos、sentinel

新建 provider-payment9003 / 9004 两个 module
除了端口号不同,其他均相同

8.1.1 提供 9003 / 9004

pom.xml
	<dependencies>
        <!-- SpringCloud ailibaba 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>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
配置文件

注意更改端口号

server:
  port: 9003

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

management:
  endpoints:
    web:
      exposure:
        include: '*'
主启动
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9003 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain9003.class, args);
    }
}
业务类
@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;
    }
}

8.1.2 消费者 84

新建 module

pom.xml
    <artifactId>consumer-order84</artifactId>

    <dependencies>
        <!--SpringCloud openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</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>
        <!-- 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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
配置文件
server:
  port: 84

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

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

# 激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true
主启动
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderNacosMain84
{
    public static void main(String[] args) {
        SpringApplication.run(OrderNacosMain84.class, args);
    }
}
ribbon配置类
@Configuration
public class ApplicationContextConfig
{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}
业务类
@RestController
@Slf4j
public class CircleBreakerController {
    public static final String SERVICE_URL = "http://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;
    }

}

@SentinelResource 注解中的属性,fallback 管运行出现的异常,blockHandler 管对 sentinel 控制台配置违规

测试

访问 http://localhost:84/consumer/fallback/1

发现其调用两个提供者是采用 轮询规则

8.1.3 服务熔断无配置

当使用注解 @SentinelResource(value = "fallback")的时候,注解属性没有熔断,也没有降级配置

id = 4时,就会给用户返回错误 500 页面

@RestController
@Slf4j
public class CircleBreakerController {
    public static final String SERVICE_URL = "http://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;
    }

}

8.1.4 只配置 fallback

使用注解 @SentinelResource(value = "fallback",fallback = "handlerFallback"),此时 fallback 属性是代码出现异常时的兜底方法

id = 4时,不会再出现 ErrorPage ,而是执行fallback属性指定的兜底方法

如下图

在这里插入图片描述

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

    @Resource
    private RestTemplate restTemplate;

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

}

8.1.5 只配置blockHandler

使用注解 @SentinelResource(value = "fallback",blockHandler = "blockHandler"),此时 blockHandler只负责sentinel控制台配置违规

id = 4时,还是会返回错误 500 页面,因为 blockHandler 对业务异常不会处理

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

    @Resource
    private RestTemplate restTemplate;

    //blockHandler只负责sentinel控制台配置违规
    @RequestMapping("/consumer/fallback/{id}")
    @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 ("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);
    }
}

配置:

在这里插入图片描述

当多次访问服务时,且 id = 4 ,则满足了异常数 > 2 的 sentinel 控制台配置,就会执行 blockHandler 兜底方法

在这里插入图片描述

8.1.6 fallback和blockHandler都配置

@RestController
@Slf4j
public class CircleBreakerController {
    public static final String SERVICE_URL = "http://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);
    }
}

配置:

在这里插入图片描述

当访问 id = 4 时,一秒一次时,正常会执行 fallback 的兜底方法,因为有业务异常

但是当一秒内访问多次,违背了 sentinel 控制台配置时,会执行 blockHandler 的兜底方法,可见blockHandler 的级别更高

在这里插入图片描述

8.1.7 异常忽略

当使用 @SentinelResource 注解的方法里面,出现了注解属性exceptionsToIgnore指定的异常,就会直接显示 ErrorPage 到控制台前台,不会执行兜底方法

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

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

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",
            exceptionsToIgnore = {IllegalArgumentException.class})
    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);
    }
}

8.2 Feign系列

修改消费者 84

pom 需要加入 openFeign 依赖

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

配置文件:需要激活 Sentinel对Feign的支持

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

只有配置了 feign.sentinel.enabled=true
簇点链路里面才会显示 远程调用的 资源路径

在这里插入图片描述

主启动类:需要加上 @EnableFeignClients注解

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

业务类:需要有 Feign 的接口和实现类

@FeignClient(value = "payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
@Component
public class PaymentFallbackService implements PaymentService {
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}

controller 方法需要引入 feign 类

@RestController
@Slf4j
public class CircleBreakerController {

    @Resource
    private PaymentService paymentService;

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

测试:
此时消费者服务对于两个提供者,也是采用轮询策略
在这里插入图片描述

测试 84 调用9003,,此时故意关闭 9003 和 9004 服务提供者,看 84 消费自动降级,不会被耗死

在这里插入图片描述
如果仅仅是关闭 9003,而运行着9004,84 就会去找 9004,避免单点故障

8.3、熔断框架比较

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

9、规则持久化 - 使用 Nacos 配置规则

官网

是什么:

一旦我们重启服务,sentinel 规则就会消失,生产环境需要将配置规则持久化

怎么玩:

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

DataSource 扩展常见的实现方式有:

  • 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更;
  • 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。
    Sentinel 目前支持以下数据源扩展:

Pull-based:动态文件数据源、Consul, Eureka
Push-based:ZooKeeper, Redis, Nacos, Apollo, etcd

1)、新建module

sentinel-service8401

参考官网

1 pom.xml

主要的是这个依赖

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

完整依赖信息如下:

<dependencies>
	<dependency>
	<!--  引入自己定义的api通用包,可以使用Payment支付Entity  -->
		<groupId>com.atguigu.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-datasource-nacos 做持久化用到 -->
	<dependency>
		<groupId>com.alibaba.csp</groupId>
		<artifactId>sentinel-datasource-nacos</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>
	<!-- 日常通用jar包配置 -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-devtools</artifactId>
	<scope>runtime</scope>
	<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>cn.hutool</groupId>
		<artifactId>hutool-all</artifactId>
	<version>4.6.3</version>
	</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>

2 配置文件

主要的是

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        port: 8719
      datasource:
      # ds,ds1 是随便写的
        ds:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

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

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

若是配置类,可参考如下:

在这里插入图片描述

@Configuration
public class SentinelConfig {
    public SentinelConfig() {
        //1、加载流控策略
        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new
                NacosDataSource<>("127.0.0.1:8848", "demo", "sentinel",
                source -> JSON.parseObject(source, new
                        TypeReference<List<FlowRule>>() {
                        }));
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
        
        //2、加载降级策略
        ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource =
                new NacosDataSource<>("127.0.0.1:8848", "demo", "sentinel",
                        source -> JSON.parseObject(source, new
                                TypeReference<List<DegradeRule>>() {
                                }));
        DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
        
        //3、加载系统规则
        ReadableDataSource<String, List<SystemRule>> systemRuleDataSource =
                new NacosDataSource<>("127.0.0.1:8848", "demo", "sentinel",
                        source -> JSON.parseObject(source, new
                                TypeReference<List<SystemRule>>() {
                                }));
        SystemRuleManager.register2Property(systemRuleDataSource.getProperty());
        
        //4、加载权限策略
        ReadableDataSource<String, List<AuthorityRule>>
                authorityRuleDataSource = new NacosDataSource<>("127.0.0.1:8848", "demo",
                "sentinel",
                source -> JSON.parseObject(source, new
                        TypeReference<List<AuthorityRule>>() {
                        }));
        AuthorityRuleManager.register2Property(authorityRuleDataSource.getProperty(
        ));
    }
}

在 nacos 中创建 dataId,并使用 json 格式

在这里插入图片描述

2)、添加Nacos规则配置

在这里插入图片描述
配置内容如下:

添加一条流控规则测试

[
	{
		"resource":"/rateLimit/byUrl",
		"limitApp":"default",
		"grade":1,
		"count":1,
		"strategy":0,
		"controlBehavior":0,
		"clusterMode":false
	}
]

内容解析:

  • resource:资源名,即限流规则的作用对
  • limitApp:来源应用,流控针对的调用来源,若为 default 则不区分调用来源
  • grade:阈值类型,0表示线程数,1表示QPS。限流阈值类型(QPS 或并发线程数)
  • count:单机阈值
  • strategy:流控模式,0表示直接,1表示关联,2表示链路
  • controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
  • clusterMode:是否集群

系统规则,降级规则等均可添加

[
	{
		"resource":"/rateLimit/byUrl",
		"limitApp":"default",
		"grade":1,
		"count":1,
		"strategy":0,
		"controlBehavior":0,
		"clusterMode":false
	}
	{
		"highestSystemLoad": -1, 
		"highestCpuUsage": 0.99, 
		"qps": 2, 
		"avgRt": 10, 
		"maxThread": 10
	}
]

3)、测试

此时启动 8401 服务,打开 sentinel 控制台,发现已经有了 流控规则,不需要自己手动配置

在这里插入图片描述

现在重启 8401 服务,需要先多次调用一下 8401 的访问地址localhost:8401/rateLimit/byUrl,发现 sentinel 控制台的流控规则出现了

持久化成功👍

注意:
Sentinel 控制台改变流控规则,不能推送到 nacos 中,
Nacos 中改变流控规则可以实时观察到变化

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值