# SA实战 ·《SpringCloud Alibaba实战》第11章-服务容错加餐:Sentinel核心技术与配置规则(最全使用教程)

作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)

大家好,我是冰河~~

一不小心《SpringCloud Alibaba实战 (opens new window)》专栏都更新到第11章了,再不上车就跟不上了,小伙伴们快跟上啊!

注意:本项目完整源码加入 冰河技术 (opens new window) 知识星球即可获取,文末有优惠券。

在《SpringCloud Alibaba实战 (opens new window)》专栏前面的文章中,我们实现了用户微服务、商品微服务和订单微服务之间的远程调用,并且实现了服务调用的负载均衡。也基于阿里开源的Sentinel实现了服务的限流与容错。今天,就和大家一起来聊聊Sentinel的核心技术与配置规则,这应该是全网最全的Sentinel使用教程了吧。

# 本章总览

文章有点长呀,小伙伴们耐心看完,并跟着实操每一个案例,相信你一定会对Sentinel有一个全新的认识。

sa-2022-05-05-054

# Sentinel核心功能

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

# 流量控制

在高并发、大流量场景下,进入系统的流量如果不加控制的话,系统就很有可能会被流量压垮。所以,在流量正式进入系统之前,需要对流量进行控制,以便使流量均匀、可控的方式进入系统。

Sentinel作为一个非常出色的容错组件,能够将不可控的流量经过处理转化成均匀、可控的流量。

# 熔断降级

如果检测到系统中的某个调用链路中某个节点出现故障,比如请求超时、服务宕机或者异常比超出一定阈值时,就会对出现故障的节点的调用频率进行限制,甚至不调用出现故障的节点,让请求能够快速失败并返回,以最大程度避免影响到其他节点的服务而导致系统的级联故障。

Sentinel主要通过 限制并发线程数和响应时间 对资源的访问进行降级。

1.限制并发线程数进行降级

Sentinel可以通过限制服务节点的并发线程数量,来减少对其他服务节点的影响。例如,当某个服务节点出现故障,例如响应时间变长,或者直接宕机。此时,对服务的直接影响就是会造成请求线程数的不断堆积。如果这些堆积的线程数达到一定的数量后,对当前服务节点的后续请求就会被拒绝,等到堆积的线程完成任务后再开始继续接收新的请求。

2.通过响应时间进行降级

Sentinel除了可以通过限制并发线程数进行降级外,也能够通过响应时间进行降级。如果依赖的服务出现响应时间过长的情况,则所有对该服务的请求都会被拒绝,直到过了指定的时间窗口之后才能再次访问该服务。

# 系统负载保护

Sentinel提供了系统维度的自适应保护能力。当系统的压力和负载比较高的时候,如果还持续让大量的请求进入系统,此时就有可能将系统压垮,进而导致系统宕机。Sentinel会在集群环境下,将本应服务器A承载的流量转发到其他服务器上,比如转发到服务器B上。如果此时服务器B也处于高负载的状态,则Sentinel会提供相应的保护机制,让系统的入口流量和系统的整体负载达到平衡,让系统整体可用,并且能够最大限度的处理请求。

# Sentinel核心规则

Sentinel的核心规则包括流控规则、熔断规则、热点规则、授权规则和系统规则,每种规则的配置方式不同。接下来,就详细介绍下Sentinel中的每种规则的作用与效果。

sa-2022-05-05-039

# 流控规则

Sentinel能够对流量进行控制,主要是监控应用的QPS流量或者并发线程数等指标,如果达到指定的阈值时,就会被流量进行控制,以避免服务被瞬时的高并发流量击垮,保证服务的高可靠性。

# 簇点链路规则

(1)点击簇点链路菜单,可以看到之前访问过的接口,如下所示。

sa-2022-05-05-001

(2)点击右侧的流控按钮,会弹出新增流控规则的提示框,如下所示。

sa-2022-05-05-002

这里,每个配置项的说明如下所示。

  • 资源名:资源的唯一名称,默认就是请求的接口路径,可以自行修改,但是要保证唯一。
  • 针对来源:具体针对某个微服务进行限流,默认值为default,表示不区分来源,全部限流。
  • 阈值类型:QPS表示通过QPS进行限流,并发线程数表示通过并发线程数限流。
  • 单机阈值:与阈值类型组合使用。如果阈值类型选择的是QPS,表示当调用接口的QPS达到阈值时,进行限流操作。如果阈值类型选择的是并发线程数,则表示当调用接口的并发线程数达到阈值时,进行限流操作。
  • 是否集群:选中则表示集群环境,不选中则表示非集群环境。

# 配置简单限流

这里,针对http://localhost:8080/order/test_sentinel接口进行简单的配置,在新增流控规则里阈值类型选择QPS,单机阈值输入3,表示每秒钟的请求量如果超过3,则会触发Sentinel的限流操作。

sa-2022-05-05-003

点击新增按钮后,会为http://localhost:8080/order/test_sentinel接口新增一条限流规则,如下所示。

sa-2022-05-05-004

接下来,在浏览器上快速刷新http://localhost:8080/order/test_sentinel接口,当每秒钟的刷新频率超过3次时,会出现如下所示的提示信息。

sa-2022-05-05-005

# 配置流控模式

点击http://localhost:8080/order/test_sentinel接口流控规则后面的编辑按钮,打开编辑流控规则弹出框(如果是首次配置的话,就是新增流控规则弹出框),点击高级选项配置。

sa-2022-05-05-006

会显示出如下所示的界面。

sa-2022-05-05-007

可以看到,Sentinel主要提供了三种流控模式,分别为直接、关联和链路。

  • 直接:默认的流控模式,当接口达到限流条件时,直接开启限流功能。
  • 关联:当关联的资源达到限流条件时,开启限流功能。
  • 链路:当从某个接口请求过来的资源达到限流条件时,开启限流功能。

# 演示直接流控模式

Sentinel默认就是使用的直接流控模式,我们之前在订单微服务中集成的就是Sentinel的直接流控模式,在本文的流控规则-配置简单限流中,也是使用的直接流控模式,这里不再赘述。

# 演示关联流控模式

(1)在订单微服务的io.binghe.shop.order.controller.OrderController 类中新增 /test_sentinel2接口,如下所示。

@GetMapping(value = "/test_sentinel2")
public String testSentinel2(){
    log.info("测试Sentinel2");
    return "sentinel2";
}
1
2
3
4
5

(2)在浏览器上访问下http://localhost:8080/order/test_sentinel2接口,以便使Sentinel检测到http://localhost:8080/order/test_sentinel2接口。

sa-2022-05-05-008

注意:如果想使用Sentinel对某个接口进行限流和降级等操作,一定要先访问下接口,使Sentinel检测出相应的接口,这里一定要注意,在后续的文章中,不再单独说明。

(3)回到为http://localhost:8080/order/test_sentinel接口配置流控规则的页面,如下所示。

sa-2022-05-05-009

(4)在流控模式中选择关联,在关联资源中输入/test_sentinel2,如下所示。

sa-2022-05-05-010

点击保存按钮保存配置。

(5)打开JMeter,对http://localhost:8080/order/test_sentinel2接口进行测试,使其每秒钟的访问次数大于3,JMeter的具体配置如下所示。

sa-2022-05-05-011

在线程组中配置每秒访问4次。

sa-2022-05-05-012

配置完毕后,使用JMeter开始访问http://localhost:8080/order/test_sentinel2接口。

(6)在浏览器上刷新http://localhost:8080/order/test_sentinel接口,发现已经触发了Sentinel的限流功能。

sa-2022-05-05-013

至此,关联流控模式演示完毕。

# 演示链路流控模式

(1)在订单微服务的io.binghe.shop.order.service包中新增SentinelService接口,用于测试流控模式,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试Sentinel
 */
public interface SentinelService {

    /**
     * 测试方法
     */
    void sendMessage();
}
1
2
3
4
5
6
7
8
9
10
11
12

(2)在订单微服务的io.binghe.shop.order.service.impl包中新增SentinelServiceImpl类,实现SentinelService接口,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试类
 */
@Service
public class SentinelServiceImpl implements SentinelService {
    @Override
    @SentinelResource("sendMessage")
    public void sendMessage() {
        System.out.println("测试Sentinel的链路流控模式");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

这里,我们在sendMessage()方法上使用了@SentinelResource注解, @SentinelResource注解会在后续文章中介绍,这里不再赘述。

(3)在订单微服务的io.binghe.shop.order.controller包下新建SentinelController类,用于测试接口,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试Sentinel
 */
@Slf4j
@RestController
public class SentinelController {

    @Autowired
    private SentinelService sentinelService;

    @GetMapping(value = "/request_sentinel1")
    public String requestSentinel1(){
        log.info("测试Sentinel1");
        sentinelService.sendMessage();
        return "sentinel1";
    }
    @GetMapping(value = "/request_sentinel2")
    public String requestSentinel2(){
        log.info("测试Sentinel2");
        sentinelService.sendMessage();
        return "sentinel2";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

(4)升级SpringCloud Alibaba的依赖版本到2.2.7.RELEASE,升级SpringCloud版本到Hoxton.SR12,并且加入SpringBoot的管理依赖。主要的修改的是shop-springcloud-alibaba父工程的pom.xml配置,修改后的配置文件如下所示。

<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    <spring-cloud-alibaba.version>2.2.7.RELEASE</spring-cloud-alibaba.version>
    <spring.boot.version>2.3.12.RELEASE</spring.boot.version>
    <logback.version>1.1.7</logback.version>
    <slf4j.version>1.7.21</slf4j.version>
    <common.logging>1.2</common.logging>
    <fastjson.version>1.2.51</fastjson.version>
    <mybatis.version>3.4.6</mybatis.version>
    <mybatis.plus.version>3.4.1</mybatis.plus.version>
    <mysql.jdbc.version>8.0.19</mysql.jdbc.version>
    <druid.version>1.1.10</druid.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>${spring.boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

(5)升级Nacos,将Nacos注册中心由1.4.3版本升级为2.1.0版本,并进入Nacos的bin目录,输入如下命令启动Nacos。

startup.cmd -m standalone
1

(6)在订单微服务的application.yml文件中新增链路配置,如下所示。

spring:
  cloud:
    sentinel:
      web-context-unify: false
1
2
3
4

(7)在浏览器中分别访问http://localhost:8080/order/request_sentinel1http://localhost:8080/order/request_sentinel2,查看Sentinel中的簇点链路,如下所示。

sa-2022-05-05-014

(8)点击sendMessage后面的流控按钮,如下所示。

sa-2022-05-05-015

(9)在弹出的新增流控规则编辑框中阈值类型选择QPS,单机阈值输入3,在打开的高级选项中的流控模式选择链路,入口资源输入/request_sentinel1,如下所示。

sa-2022-05-05-016

点击新增按钮保存配置,此时,如果是通过http://localhost:8080/order/request_sentinel1接口调用io.binghe.shop.order.service.SentinelService#sendMessage()方法时,如果调用的频率每秒钟超过3次,就会触发Sentinel的限流操作,而通过http://localhost:8080/order/request_sentinel2接口调用io.binghe.shop.order.service.SentinelService#sendMessage()方法时,则不受限制。

(10)在浏览器中不断访问http://localhost:8080/order/request_sentinel1,使得每秒的访问次数超过3,则会触发Sentinel的限流操作,如下所示。

sa-2022-05-05-017

访问http://localhost:8080/order/request_sentinel2接口则不会被限流。

至此,链路流控模式演示完毕。

# 附加说明

在流控规则的高级选项中还有三个流控效果,如下所示。

sa-2022-05-05-018

接下来,就对这三个选项进行简单的说明。

  • 快速失败:会直接失败,抛出异常,期间不会做任何其他的处理操作。

sa-2022-05-05-019

  • Warm Up:从开始阈值到最大QPS阈值会有一个缓冲,可以设置一个预热时长,这个选项比较适合突发瞬时高并发流量的场景,能够将突发的高并发流量转换为均匀、缓慢增长的场景。

sa-2022-05-05-020

  • 排队等待:能够使请求均匀的通过,单机的阈值为每秒通过的请求数量,其余的请求会排队等待。另外,还会设置一个超时时间,当请求超过超时时间未处理时,会被丢弃。

sa-2022-05-05-021

# 熔断规则

降级规则一般情况下,指的是满足某些条件时,对服务进行降级操作。

# 熔断规则概述

Sentinel主要提供了三个熔断策略,分别为:慢调用比例、异常比例和异常数。

sa-2022-05-05-033

# 演示基于慢调用比例熔断

(1)首先在浏览器中访问http://localhost:8080/order/request_sentinel2,在Sentinel的簇点链路里找到/request_sentinel2

sa-2022-05-05-030

(2)点击熔断按钮,进入熔断规则配置框,按照如下方式进行配置。

sa-2022-05-05-031

上述配置表示最大响应时长为1ms,比例阈值达到0.1时,会触发熔断操作,并且熔断的时长为2,最小请求数未5,统计的时长为1000毫秒。

(3)点击新增按钮后,不断在浏览器中刷新http://localhost:8080/order/request_sentinel2,会发现触发了Sentinel的熔断操作,如下所示。

sa-2022-05-05-032

# 演示基于异常比例熔断

(1)在订单微服务的io.binghe.shop.order.controller.SentinelController类中定义一个成员变量count,用来记录访问计数,同时,新增一个requestSentinel4()方法用来测试基于异常比例的熔断,如下所示。

private int count = 0;
@GetMapping(value = "/request_sentinel4")
@SentinelResource("request_sentinel4")
public String requestSentinel4(){
    log.info("测试Sentinel4");
    count++;
    //模拟异常,比例为50%
    if (count % 2 == 0){
        throw new RuntimeException("演示基于异常比例熔断");
    }
    return "sentinel4";
}
1
2
3
4
5
6
7
8
9
10
11
12

(2)首先在浏览器中访问http://localhost:8080/order/request_sentinel4,在Sentinel的簇点链路里找到/request_sentinel4。

sa-2022-05-05-034

(3)点击熔断按钮,进入熔断规则配置框,按照如下方式进行配置。

sa-2022-05-05-035

io.binghe.shop.order.controller.SentinelController#requestSentinel4()方法中,设置的异常比例为50%,这里在Sentinel的比例阈值中设置的异常比例为0.3,也就是30%,所以,在访问http://localhost:8080/order/request_sentinel4接口时,会触发Sentinel的熔断操作。

(4)点击新增按钮后,不断在浏览器中刷新http://localhost:8080/order/request_sentinel4,会发现触发了Sentinel的熔断操作,如下所示。

sa-2022-05-05-036

# 演示基于异常数熔断

(1)首先在浏览器中访问http://localhost:8080/order/request_sentinel4,在Sentinel的簇点链路里找到/request_sentinel4。

sa-2022-05-05-034

(2)点击熔断按钮,进入熔断规则配置框,按照如下方式进行配置。

sa-2022-05-05-037

上述配置表示,在1秒钟内最少请求2次,当异常数大于1时,会触发熔断操作,熔断的时长为5秒。

(3)点击保存按钮后,不断在浏览器中刷新http://localhost:8080/order/request_sentinel4,会发现触发了Sentinel的熔断操作,如下所示。

sa-2022-05-05-038

# 热点规则

Sentinel的热点规则可以将流量规则控制到具体的参数上。

# 热点规则概述

Sentinel的热点规则可以根据具体的参数来控制流量规则,适用于根据不同参数进行流量控制的场景。

# 演示热点规则

(1)在订单微服务的io.binghe.shop.order.controller.SentinelController类中新增requestSentinel3()方法,其中会带有一个String类型的参数header和一个String类型的参数body,如下所示。

@GetMapping(value = "/request_sentinel3")
@SentinelResource("request_sentinel3")
public String requestSentinel3(String header, String body){
    log.info("测试Sentinel3");
    return "sentinel3";
}
1
2
3
4
5
6

(2)在浏览器中访问http://localhost:8080/order/request_sentinel3接口,在Sentinel的簇点链路中会显示/request_sentinel3接口,如下所示。

sa-2022-05-05-022

(3)点击热点按钮,如下所示。

sa-2022-05-05-023

(4)在弹出的热点规则配置框中的参数索引中输入0,单机阈值输入1,统计窗口时长输入1,如下所示。

sa-2022-05-05-024

表示对requestSentinel3()方法的第一个参数header进行限流,如果每秒钟访问的次数超过1次,则触发限流。

(5)保存配置后,在浏览器中不断访问http://localhost:8080/order/request_sentinel3?header=header,当每秒访问的频率超过1次时,会触发Sentinel的限流操作,如下所示。

sa-2022-05-05-025

不断访问http://localhost:8080/order/request_sentinel3?body=body,则不会触发限流操作。

sa-2022-05-05-026

# 演示热点高级选项规则

(1)在弹出的热点规则配置框中打开高级选项,在参数类型中选择java.lang.String,因为在参数索引中输入0,表示的是对header参数限流,而header参数是String类型的。在参数值里输入header,也就是为参数名为header的参数赋值为字符串header。限流阈值为1,如下所示。

sa-2022-05-05-027

(2)点击添加按钮后如下所示。

sa-2022-05-05-028

(3)点击保存按钮,在浏览器不断刷新http://localhost:8080/order/request_sentinel3?header=header,会触发Sentinel的限流操作。

sa-2022-05-05-029

# 授权规则

授权规则能够根据调用来源判断还否允许执行本次请求。

# 授权规则概述

在某些场景下,需要根据调用接口的来源判断是否允许执行本次请求。此时就可以使用Sentinel提供的授权规则来实现,Sentinel的授权规则能够根据请求的来源判断是否允许本次请求通过。

在Sentinel的授权规则中,提供了 白名单与黑名单 两种授权类型。

# 演示授权规则

(1)在订单微服务shop-order中新建io.binghe.shop.order.parser包,并创建MyRequestOriginParser类,实现com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser接口,用来处理请求的来源。代码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description Sentinel授权规则,用来处理请求的来源
 */
@Component
public class MyRequestOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        return httpServletRequest.getParameter("serverName");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

(2)首先在浏览器中访问http://localhost:8080/order/request_sentinel4,在Sentinel的簇点链路里找到/request_sentinel4。

sa-2022-05-05-034

(3)点击授权按钮,进入授权规则配置框,按照如下方式进行配置。

sa-2022-05-05-040

其中,流控应用填写的是test,授权类型为黑名单。这里要结合新建的MyRequestOriginParser类进行理解,MyRequestOriginParser类的parseOrigin()方法如下所示。

public String parseOrigin(HttpServletRequest httpServletRequest) {
    return httpServletRequest.getParameter("serverName");
}
1
2
3

parseOrigin()方法中直接返回了从HttpServletRequest中获取的serverName参数,而在上图中的流控应用中输出的是test,授权类型为黑名单。

所以,如果我们访问http://localhost:8080/order/request_sentinel4?serverName=test的话,是处于黑名单的状态,无法访问。

(4)点击新增按钮后,不断在浏览器中刷新http://localhost:8080/order/request_sentinel4?serverName=test,会发现无法访问,被Sentinel限流了。

sa-2022-05-05-041

# 系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、 RT、入口 QPS 、 CPU使用率和线程数五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

# 系统规则概述

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。

  • Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
  • RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
  • CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护

# 演示系统规则

(1)在订单微服务中新建io.binghe.shop.order.handler包,并创建MyUrlBlockHandler类,实现com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler接口,用来捕获系统级Sentinel异常,代码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 处理Sentinel系统规则,返回自定义异常
 */
@Component
public class MyUrlBlockHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String msg = null;
        if (e instanceof FlowException) {
            msg = "限流了";
        } else if (e instanceof DegradeException) {
            msg = "降级了";
        } else if (e instanceof ParamFlowException) {
            msg = "热点参数限流";
        } else if (e instanceof SystemBlockException) {
            msg = "系统规则(负载/...不满足要求)";
        } else if (e instanceof AuthorityException) {
            msg = "授权规则不通过";
        }
        // http状态码
        response.setStatus(500);
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        response.setContentType("application/json;charset=utf-8");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", 500);
        jsonObject.put("codeMsg", msg);
        response.getWriter().write(jsonObject.toJSONString());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

(2)在订单微服务的io.binghe.shop.order.controller.SentinelController类中新增requestSentinel5()方法,如下所示。

@GetMapping(value = "/request_sentinel5")
@SentinelResource("request_sentinel5")
public String requestSentinel5(){
    log.info("测试Sentinel5");
    return "sentinel5";
}
1
2
3
4
5
6

(3)首先在浏览器中访问http://localhost:8080/order/request_sentinel5,在Sentinel的簇点链路里找到/request_sentinel5。

sa-2022-05-05-034

(4)点击流控按钮,进入流控规则配置框,按照如下方式进行配置。

sa-2022-05-05-042

(5)在浏览器中不断刷新http://localhost:8080/order/request_sentinel5,会显示如下信息。

sa-2022-05-05-044

返回的原始数据如下所示。

{"code":500,"codeMsg":"限流了"}
1

说明触发了系统规则,捕获到了Sentinel全局异常。

# @SentinelResource注解

使用Sentinel时,可以使用@SentinelResource注解来指定异常处理策略。

# @SentinelResource注解概述

在Sentinel中,指定发生异常时的处理策略非常简单,只需要使用@SentinelResource注解即可,@SentinelResource注解的源码如下所示。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {

	//资源名称
    String value() default "";
	//entry类型,标记流量的方向,取值IN/OUT,默认是OUT
    EntryType entryType() default EntryType.OUT;
    int resourceType() default 0;
	//处理BlockException的函数名称,函数要求:
    //1. 必须是 public
    //2.返回类型 参数与原方法一致
    //3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置
    //blockHandlerClass ,并指定blockHandlerClass里面的方法。
    String blockHandler() default "";
	
    //存放blockHandler的类,对应的处理函数必须static修饰。
    Class<?>[] blockHandlerClass() default {};
	
    //用于在抛出异常的时候提供fallback处理逻辑。 fallback函数可以针对所
    //有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求:
    //1. 返回类型与原方法一致
    //2. 参数类型需要和原方法相匹配
    //3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定fallbackClass里面的方法。
    String fallback() default "";
	
    //存放fallback的类。对应的处理函数必须static修饰。
    String defaultFallback() default "";

	//用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进
    //行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求:
    //1. 返回类型与原方法一致
    //2. 方法参数列表为空,或者有一个 Throwable 类型的参数。
    //3. 默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定 fallbackClass 里面的方法。
    Class<?>[] fallbackClass() default {};

  	//指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。
    Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
    
	//需要trace的异常
    Class<? extends Throwable>[] exceptionsToIgnore() default {};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 演示@SentinelResource注解

1.定义限流和降级后的处理方法

(1)在订单微服务的io.binghe.shop.order.service.SentinelService接口中新增sendMessage2()方法,如下所示。

String sendMessage2();
1

(2)在订单微服务的io.binghe.shop.order.service.impl.SentinelServiceImpl方法中,实现sendMessage2()方法,并且定义一个成员变量count,用来记录请求sendMessage2()方法的次数,同时定义25%的异常率。在sendMessage2()方法上使用@SentinelResource指定了资源的名称、发生BlockException时进入的方法和发生异常时进入的方法,代码如下所示。

private int count = 0;

@Override
@SentinelResource(
    value = "sendMessage2",
    blockHandler = "blockHandler",
    fallback = "fallback")
public String sendMessage2() {
    count ++;
    //25%的异常率
    if (count % 4 == 0){
        throw new RuntimeException("25%的异常率");
    }
    return "sendMessage2";
}

public String blockHandler(BlockException e){
    log.error("限流了:{}", e);
    return "限流了";
}

public String fallback(Throwable e){
    log.error("异常了:{}", e);
    return "异常了";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

(3)在订单微服务的io.binghe.shop.order.controller.SentinelController类中新增requestSentinel6()方法,在方法中调用io.binghe.shop.order.service.SentinelService接口中的sendMessage2()方法,如下所示。

@GetMapping(value = "/request_sentinel6")
public String requestSentinel6(){
    log.info("测试Sentinel6");
    return sentinelService.sendMessage2();
}
1
2
3
4
5

(4)首先在浏览器中访问http://localhost:8080/order/request_sentinel6,在Sentinel的簇点链路里找到/request_sentinel6。

sa-2022-05-05-045

(5)点击流控按钮进入流控规则页面,按照下图方式进行配置。

sa-2022-05-05-046

(6)点击新增按钮后在浏览器中刷新http://localhost:8080/order/request_sentinel6,当刷新的频率超过每秒2次时,浏览器会显示如下信息。

sa-2022-05-05-048

当刷新的次数是4的倍数时,浏览器会显示如下信息。

sa-2022-05-05-047

2.在外部类中指定限流和异常调用的方法

(1)在订单微服务的io.binghe.shop.order.handler包下新建MyBlockHandlerClass类,用于定义被Sentinel限流时的方法,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 定义被Sentinel限流时调用的方法
 */
@Slf4j
public class MyBlockHandlerClass {

    public static String blockHandler(BlockException e){
        log.error("限流了:{}", e);
        return "限流了";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

(2)在订单微服务的io.binghe.shop.order.handler包下新建MyFallbackClass类,用于定义抛出异常时调用的方法,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 定义异常时调用的方法
 */
@Slf4j
public class MyFallbackClass {

    public static String fallback(Throwable e){
        log.error("异常了:{}", e);
        return "异常了";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

(3)修改io.binghe.shop.order.service.impl.SentinelServiceImpl#sendMessage2()方法上的注解,修改后的代码如下所示。

@Override
@SentinelResource(
    value = "sendMessage2",
    blockHandlerClass = MyBlockHandlerClass.class,
    blockHandler = "blockHandler",
    fallbackClass = MyFallbackClass.class,
    fallback = "fallback")
public String sendMessage2() {
    count ++;
    System.out.println(count);
    //25%的异常率
    if (count % 4 == 0){
        throw new RuntimeException("25%的异常率");
    }
    return "sendMessage2";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

(4)首先在浏览器中访问http://localhost:8080/order/request_sentinel6,在Sentinel的簇点链路里找到/request_sentinel6。

sa-2022-05-05-045

(5)点击流控按钮进入流控规则页面,按照下图方式进行配置。

sa-2022-05-05-046

(6)点击新增按钮后在浏览器中刷新http://localhost:8080/order/request_sentinel6,当刷新的频率超过每秒2次时,浏览器会显示如下信息。

sa-2022-05-05-048

当刷新的次数是4的倍数时,浏览器会显示如下信息。

sa-2022-05-05-047

# Sentinel持久化

Sentinel中可以自定义配置的持久化来将Sentinel的配置规则持久化到服务器磁盘,使得重启应用或者Sentinel后,Sentinel的配置规则不丢失。

# Sentinel持久化概述

细心的小伙伴会发现,我们之前配置的Sentinel规则在程序重启或者Sentinel重启后就会消失不见,此时就需要我们重新配置。如果这发生在高并发、大流量的场景下是不可接受的。那有没有什么办法让程序或Sentinel重启后配置不丢失呢?其实,Sentinel中可以自定义配置的持久化来解决这个问题。

# 实现Sentinel的持久化

(1)在订单微服务shop-order中新建io.binghe.shop.order.persistence包,并创建SentinelPersistenceRule类,实现com.alibaba.csp.sentinel.init.InitFunc接口,并在SentinelPersistenceRule类中获取应用的名称,覆写init()方法,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description Sentinel规则持久化
 */
public class SentinelPersistenceRule implements InitFunc {
    //实际可以从外部配置读取
    private String appcationName = "server-order";
    @Override
    public void init() throws Exception {
        String ruleDir = System.getProperty("user.home") + "/sentinel-rules/" + appcationName;
        String flowRulePath = ruleDir + "/flow-rule.json";
        String degradeRulePath = ruleDir + "/degrade-rule.json";
        String systemRulePath = ruleDir + "/system-rule.json";
        String authorityRulePath = ruleDir + "/authority-rule.json";
        String paramFlowRulePath = ruleDir + "/param-flow-rule.json";

        this.mkdirIfNotExits(ruleDir);
        this.createFileIfNotExits(flowRulePath);
        this.createFileIfNotExits(degradeRulePath);
        this.createFileIfNotExits(systemRulePath);
        this.createFileIfNotExits(authorityRulePath);
        this.createFileIfNotExits(paramFlowRulePath);

        // 流控规则
        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
                flowRulePath,
                flowRuleListParser
        );
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
                flowRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);

        // 降级规则
        ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
                degradeRulePath,
                degradeRuleListParser
        );
        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
        WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
                degradeRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);

        // 系统规则
        ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
                systemRulePath,
                systemRuleListParser
        );
        SystemRuleManager.register2Property(systemRuleRDS.getProperty());
        WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
                systemRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);

        // 授权规则
        ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
                authorityRulePath,
                authorityRuleListParser
        );
        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
        WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
                authorityRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);

        // 热点参数规则
        ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
                paramFlowRulePath,
                paramFlowRuleListParser
        );
        ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
        WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
                paramFlowRulePath,
                this::encodeJson
        );
        ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
    }

    private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<FlowRule>>() {
            }
    );
    private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<DegradeRule>>() {
            }
    );
    private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<SystemRule>>() {
            }
    );

    private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<AuthorityRule>>() {
            }
    );

    private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<ParamFlowRule>>() {
            }
    );

    private void mkdirIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    private void createFileIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
        }
    }

    private <T> String encodeJson(T t) {
        return JSON.toJSONString(t);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131

(2)在订单微服务的resources目录下新建META-INF目录,并在META-INF目录下新建services目录,在services目录下新建名称为com.alibaba.csp.sentinel.init.InitFunc的文件,如下所示。

sa-2022-05-05-049

(3)在com.alibaba.csp.sentinel.init.InitFunc文件中添加io.binghe.shop.order.persistence.SentinelPersistenceRule类的全类名,如下所示。

io.binghe.shop.order.persistence.SentinelPersistenceRule
1

sa-2022-05-05-050

(4)首先在浏览器中访问http://localhost:8080/order/request_sentinel6,在Sentinel的簇点链路里找到/request_sentinel6。

sa-2022-05-05-045

(5)点击流控按钮进入流控规则页面,按照下图方式进行配置。

sa-2022-05-05-046

(6)点击新增按钮,此时打开电脑的user.home目录,我电脑的目录为C:\Users\binghe,可以发现C:\Users\binghe目录中多了一个sentinel-rules目录。

sa-2022-05-05-051

(7)打开sentinel-rules目录,发现里面存在一个server-order目录,如下所示。

sa-2022-05-05-052

(8)打开server-order目录后,会发现生成了Sentinel的配置文件,并持久化到了磁盘上,如下所示。

sa-2022-05-05-053

(9)打开flow-rule.json文件,内容如下所示。

[
    {
        "clusterConfig": {
            "acquireRefuseStrategy": 0,
            "clientOfflineTime": 2000,
            "fallbackToLocalWhenFail": true,
            "resourceTimeout": 2000,
            "resourceTimeoutStrategy": 0,
            "sampleCount": 10,
            "strategy": 0,
            "thresholdType": 0,
            "windowIntervalMs": 1000
        },
        "clusterMode": false,
        "controlBehavior": 0,
        "count": 2,
        "grade": 1,
        "limitApp": "default",
        "maxQueueingTimeMs": 500,
        "resource": "/request_sentinel6",
        "strategy": 0,
        "warmUpPeriodSec": 10
    }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

可以看到,flow-rule.json文件中持久化了对于/request_sentinel6接口的配置。

至此,我们完成了Sentinel规则的持久化。

# 星球服务

加入星球,你将获得:

1.项目学习:微服务入门必备的SpringCloud Alibaba实战项目、手写RPC项目—所有大厂都需要的项目【含上百个经典面试题】、深度解析Spring6核心技术—只要学习Java就必须深度掌握的框架【含数十个经典思考题】、Seckill秒杀系统项目—进大厂必备高并发、高性能和高可用技能。

2.框架源码:手写RPC项目—所有大厂都需要的项目【含上百个经典面试题】、深度解析Spring6核心技术—只要学习Java就必须深度掌握的框架【含数十个经典思考题】。

3.硬核技术:深入理解高并发系列(全册)、深入理解JVM系列(全册)、深入浅出Java设计模式(全册)、MySQL核心知识(全册)。

4.技术小册:深入理解高并发编程(第1版)、深入理解高并发编程(第2版)、从零开始手写RPC框架、SpringCloud Alibaba实战、冰河的渗透实战笔记、MySQL核心知识手册、Spring IOC核心技术、Nginx核心技术、面经手册等。

5.技术与就业指导:提供相关就业辅导和未来发展指引,冰河从初级程序员不断沉淀,成长,突破,一路成长为互联网资深技术专家,相信我的经历和经验对你有所帮助。

冰河的知识星球是一个简单、干净、纯粹交流技术的星球,不吹水,目前加入享5折优惠,价值远超门票。加入星球的用户,记得添加冰河微信:hacker_binghe,冰河拉你进星球专属VIP交流群。

# 星球重磅福利

跟冰河一起从根本上提升自己的技术能力,架构思维和设计思路,以及突破自身职场瓶颈,冰河特推出重大优惠活动,扫码领券进行星球,直接立减149元,相当于5折, 这已经是星球最大优惠力度!


领券加入星球,跟冰河一起学习《SpringCloud Alibaba实战》、《手撸RPC专栏》和《Spring6核心技术》,更有已经上新的《大规模分布式Seckill秒杀系统》,从零开始介绍原理、设计架构、手撸代码。后续更有硬核中间件项目和业务项目,而这些都是你升职加薪必备的基础技能。

100多元就能学这么多硬核技术、中间件项目和大厂秒杀系统,如果是我,我会买他个终身会员!

# 其他方式加入星球

特别提醒: 苹果用户进圈或续费,请加微信 hacker_binghe 扫二维码,或者去公众号 冰河技术 回复 星球 扫二维码加入星球。

# 星球规划

后续冰河还会在星球更新大规模中间件项目和深度剖析核心技术的专栏,目前已经规划的专栏如下所示。

# 中间件项目

  • 《大规模分布式定时调度中间件项目实战(非Demo)》:全程手撸代码。
  • 《大规模分布式IM(即时通讯)项目实战(非Demo)》:全程手撸代码。
  • 《大规模分布式网关项目实战(非Demo)》:全程手撸代码。
  • 《手写Redis》:全程手撸代码。
  • 《手写JVM》全程手撸代码。

# 超硬核项目

  • 《从零落地秒杀系统项目》:全程手撸代码,在阿里云实现压测(已上新)。
  • 《大规模电商系统商品详情页项目》:全程手撸代码,在阿里云实现压测。
  • 其他待规划的实战项目,小伙伴们也可以提一些自己想学的,想一起手撸的实战项目。。。

既然星球规划了这么多内容,那么肯定就会有小伙伴们提出疑问:这么多内容,能更新完吗?我的回答就是:一个个攻破呗,咱这星球干就干真实中间件项目,剖析硬核技术和项目,不做Demo。初衷就是能够让小伙伴们学到真正的核心技术,不再只是简单的做CRUD开发。所以,每个专栏都会是硬核内容,像《SpringCloud Alibaba实战》、《手撸RPC专栏》和《Spring6核心技术》就是很好的示例。后续的专栏只会比这些更加硬核,杜绝Demo开发。

小伙伴们跟着冰河认真学习,多动手,多思考,多分析,多总结,有问题及时在星球提问,相信在技术层面,都会有所提高。将学到的知识和技术及时运用到实际的工作当中,学以致用。星球中不少小伙伴都成为了公司的核心技术骨干,实现了升职加薪的目标。

# 联系冰河

# 加群交流

本群的宗旨是给大家提供一个良好的技术学习交流平台,所以杜绝一切广告!由于微信群人满 100 之后无法加入,请扫描下方二维码先添加作者 “冰河” 微信(hacker_binghe),备注:星球编号

冰河微信

# 公众号

分享各种编程语言、开发技术、分布式与微服务架构、分布式数据库、分布式事务、云原生、大数据与云计算技术和渗透技术。另外,还会分享各种面试题和面试技巧。内容在 冰河技术 微信公众号首发,强烈建议大家关注。

公众号:冰河技术

# 视频号

定期分享各种编程语言、开发技术、分布式与微服务架构、分布式数据库、分布式事务、云原生、大数据与云计算技术和渗透技术。另外,还会分享各种面试题和面试技巧。

视频号:冰河技术

# 星球

加入星球 冰河技术 (opens new window),可以获得本站点所有学习内容的指导与帮助。如果你遇到不能独立解决的问题,也可以添加冰河的微信:hacker_binghe, 我们一起沟通交流。另外,在星球中不只能学到实用的硬核技术,还能学习实战项目

关注 冰河技术 (opens new window)公众号,回复 星球 可以获取入场优惠券。

知识星球:冰河技术