Sentinel介绍

这里我直接引用官方的一段话来介绍一下sentinel。随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。通过官方的描述,我们可以知道这是一个为了保护系统稳定性和微服务质量而设计的组件,用来实现流量控制,防止系统过载,保证微服务之间的稳定交互。这里我主要挑几个重点特性来介绍一下。

流量控制:对系统的入口流量进行限制,防止系统因为过大的流量压力而崩溃,例如我们可针对某一个接口进行流量设置,1秒内只允许50个请求进入,超过这个阈值,额外的请求会被拒绝掉。

熔断和降级:如果系统的某个服务出现问题,Sentinel可以立刻断开这个服务,防止错误进一步扩大。同时,也可以实现服务的降级,在系统压力大的时候,停掉一些非关键服务,让系统的资源集中在优先度更高的任务上。

实时监控:在Sentinel控制台中,我们可以实时看到系统的运行状态,包括请求数量,拦截个数,异常等等。

项目接入

这里我创建了一个Spring Cloud Alibaba微服务架构的项目。下面是cloud版本和alibaba的版本以及springBoot的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--spring cloud版本-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2022.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring-cloud-alibaba版本-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2022.0.0.0-RC2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring-boot版本-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>

之后我们需要在需要使用Sentinel的服务中单独引入spring给我们提供的starter包即可。如果需要持久化Sentinel的配置规则需要多引入一个sentinel-datasource-nacos依赖,使用nacos作为数据源

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!--持久化Sentinel限流规则-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.6</version>
</dependency>

现在我们需要去下载Sentinel的控制台。Sentinel控制台GitHub地址 我当时下载的时候最新的稳定版本是1.8.6。后面我会以SpringBoot3.0.2的版本和Sentinel控制台v1.8.6来做演示。

控制台下载完成后,我们需要启动起来,使用java -jar启动即可,默认端口号是8080。之后在配置文件中配置一下Sentinel的控制台地址。到此,项目就完成了接入

1
2
3
4
5
6
7
8
spring:
cloud:
sentinel:
transport:
# 这里我将Sentinel控制台的端口从8080改为了8818
dashboard: 127.0.0.1:8818
# 是否支持懒加载
eager: true

浏览器中输入127.0.0.1:8080进入Sentinel控制台页面。密码和账号都是sentinel

image-20230617172904261

Sentinel的使用-流量控制

我们先来看一下Sentinel的核心功能,流量控制。

@SentinelResource注解

这个注解是用来定义资源,并且可以对过载的流量进行自定义返回值设定。下面是几个常用的属性

value:资源的名称,不可以为空

blockHandler:函数名称,如果本次请求被限流了,会去执行这个函数。该函数的参数必须和资源的参数对应,并且后面需要加一个额外的参数,类型是BlockException,同时,函数访问范围必须是public。函数的位置必须和原方法在一个类里

blockHandlerClass:这个需要和上一个属性联合使用,上一个属性如果不指定blockHandlerClass,那么只能在本类中使用,也可以写在别的类里,然后使用本属性置顶类的位置。同时,对应的函数方法必须被static修饰。

控制台流控规则设置

现在我们回到控制台,准备对该接口进行限流,我们需要先访问一次,之后在簇点链路中可以找到对应的资源名称,点击流控,可以看到如下界面。

image-20230618160815687

我们先对这几个属性进行一些简单的介绍,资源名不用多解释,就是我们设置的资源名称,针对来源类似于分组的名字,一般是默认。阈值类型有2种。单机阈值就是1秒内的请求上限。例如我们选择QPS类型,阈值设置50,就是1秒内最多只有50个请求可以成功

QPS:每秒可通过的流量次数

线程数:每秒可通过的线程数。可以理解为同时处理的请求数量

点开高级选项,可以看到有个集群选项,这里先不对集群进行介绍,我们直接看流控模式和流控效果。

image-20230618161108360

流控模式

直接:固定的对资源请求数量做限制,也是默认的流控模式

关联:点击关联后,会多一个关联资源的输入框,这里需要输入我们的关联资源名字。例如我们输入B,阈值设置为50,当B1秒内访问流量超过50,则对login资源进行限流

image-20230618161433958

链路: 同样会让我们输入链路入口资源名称,根据调用链路限流。例如,当通过B的调用资源A超过50,对整个调用链路进行限流。

流控效果

快速失败:跟名字一样,将超出的流量直接返回失败作为结果。

Warm Up:预热,先放行一小部分,随着时间推移,逐渐放大限流量。例如,我们设置1秒内1000QPS。 预热时间为10秒。那么会在第一秒放行一部分,第二秒一部分,直到第十秒放行到1000请求。

image-20230618161827271

下面是官方给出的一个示例图,可以看到流量的放行数量是逐步扩大,并不是1秒内直接放行所有的1000个请求。相当于是给系统了一个预热时间,如果前面请求的结果存入了缓存,那么后面的请求自然可以更快的返回,避免了瞬时大流量对数据库造成的压力

冷启动过程 QPS 曲线

排队等待:将流量进行排列,和队列一样,按顺序放行到后台。使用此效果的时候会让我们设置一个超时时间,如果我们设置为2秒,但2秒内该请求并没有访问到后台,那么直接和快速失败一样,返回失败。

img

代码运行结果

下面是一段演示代码,方法进入后直接返回。

1
2
3
4
5
6
7
8
9
10
11
12
@SentinelResource(value = "login", blockHandler = "sentinelDefaultMsg", blockHandlerClass = SentinelDefaultMethod.class)
@GetMapping("/login/{userId}/{password}")
public Result selectUserById(@PathVariable("userId") String userId, @PathVariable("password") String password) {;
return Result.success("SUCCESS");
}


public class SentinelDefaultMethod {
public static Result sentinelDefaultMsg(String userId, String password, BlockException blockException) {
return Result.error("访问过于频繁!");
}
}

流控规则我设置1秒内只允许1个流量通过,流控模式使用直接,效果使用快速失败。

image-20230618162829466

之后我不断访问限流的接口,可以在实时监控页面看到系统的流量拦截情况,无论我们访问多少次,1秒内只有1个请求可以顺利通过。

image-20230618162909156

同时,也会返回我们设定好的错误信息。

image-20230618162850940

现在我们尝试一下关联流控模式,增加一个update方法,将update方法作为关联资源,资源名设置为updateUser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SentinelResource(value = "login", blockHandler = "sentinelDefaultMsg", blockHandlerClass = SentinelDefaultMethod.class)
@GetMapping("/login/{userId}/{password}")
public Result selectUserById(@PathVariable("userId") String userId, @PathVariable("password") String password) {;
System.out.println("我是login接口");
return Result.success("SUCCESS");
}


@SentinelResource(value = "updateUser", blockHandler = "sentinelDefaultMsg", blockHandlerClass = SentinelDefaultMethod.class)
@GetMapping("/login/{userId}/{password}")
public Result updateUser() {
System.out.println("我是updateUser接口");
return Result.success("SUCCESS");
}

我们设置流控规则为:如果updateUser这个资源在1秒内访问流量超过1次,那么对login这个资源做流控处理。

image-20230618170505925

这里我们对updateUser这个资源做压测,在30秒内,不断访问login这个方法,看看login是否被做了流控处理。

image-20230618170617904

开启压测后,我们的login方法根本访问不通,证明Sentinel的限流规则生效,测试成功

image-20230618170719266

再放一张对照图,我们update这个方法在被高并发访问时,login的请求是全被拒绝的

image-20230618171719287

服务熔断降级

熔断是微服务中的一种保护机制,如果符合我们设定的熔断策略,就会进入熔断这一状态。在指定时间内,所有请求会被Sentinel拦截掉。指定时间结束后,进入半开状态,下一次的请求会被判断,如果不符合熔断策略。则取消熔断状态,如果还是符合熔断策略要求,继续进入熔断状态。

慢调用比例

按照程序的响应时间来进行熔断。例如下图设置,首先是比例阈值,范围是0-1,这个很好理解。例如我输入0.2,表示10个请求内至少有2个有异常则开启熔断。统计时长则表示统计请求的时间窗口大小。例如设置为1000ms,则表示在1s内,至少有1个请求进来(最小请求数)。此时开启熔断检查,如果有超过20%(比例阈值)的请求响应时间超过300ms(最大RT),则开启熔断,熔断时长为3s

image-20230709211713533

下面是一个测试接口,有50%的几率睡眠500毫秒,这是符合我们的熔断策略的。

1
2
3
4
5
6
7
8
9
10
11
12
13
@SentinelResource(value = "exception", blockHandler = "sentinelDefaultMsg", blockHandlerClass = SentinelDefaultMethod.class)
@GetMapping("/exception")
public Result exception() {
int nextInt = new Random().nextInt(100) + 1;
if (nextInt > 50) {
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return Result.success("SUCCESS");
}

使用ApiPost进行压力测试,通过请求16个,失败15000+,没有问题,因为熔断的3s内请求的状态全部为失败。

image-20230709212812438

异常比例

我们修改之前的接口,改为有50%的几率抛出异常

1
2
3
4
5
6
7
8
9
@SentinelResource(value = "exception", blockHandler = "sentinelDefaultMsg", blockHandlerClass = SentinelDefaultMethod.class)
@GetMapping("/exception")
public Result exception() {
int nextInt = new Random().nextInt(100) + 1;
if (nextInt > 50) {
throw new RuntimeException("异常");
}
return Result.success("SUCCESS");
}

同时新创建一个熔断规则,规则为1s之内有一个请求,且异常比例大于30%,则开启熔断,9s钟之内服务不可用。

image-20230709210419940

使用ApoPost进行压力测试,结果符合预期,一共就10s,第一个请求触发熔断,后续的所有请求全部失败

image-20230709211039683

异常数

根据异常数来开启熔断。在统计时长内,达到最小请求数且异常个数也达到则开启熔断。例如下图,表明在10s内,有至少5个请求且抛出了5个异常则开启熔断。因为异常数平时很少使用这里就不写案例了,做一个了解即可。

image-20230709213447316

持久化Sentinel配置

首先我们来说一下Sentinel的配置结构,配置是以JSON格式存储在nacos中的。其中又分为限流规则和熔断降级规则两种,我们先来说说限流规则。

resource:资源名

limitApp:表示流控范围,一般使用默认即可

grade:流控维度 0表示按照并发数量 1表示QPS数量(对应页面阈值类型)

count:表示允许通过的请求数(对应页面单机阈值)

strategy:表示流控模式 0 -> 直接 1-> 关联 2 -> 链路(对应界面流控模式)

controlBehavior:表示流控类型 0 -> 快速失败 1 -> Warm UP 2 -> 排队等待(对应页面流控效果)

clusterMode: 是否启用集群模式

1
2
3
4
5
6
7
8
9
10
11
12
[
{
"resource": "/login",
"limitApp": "default",
"grade": 1,
"count": 5,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]

下面是熔断降级的配置:

resource:资源名

limitApp:表示流控范围,一般使用默认即可

grade:熔断降级类型 0 -> 慢调用比例 1 -> 异常比例 2 -> 异常数比例(对应页面熔断策略)

count:表示异常比率(对应页面比例阈值)

timeWindow:表示熔断时间(对应页面熔断时长)

statIntervalMs: 统计时长(对应页面统计时长)

minRequestAmount:统计时间内的最小请求数(对应页面最小请求数)

slowRatioThreshold: 慢调用比例阈值(对应页面比例阈值,此时count变为最大RT。注意,仅在慢调用比例下生效!!!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[
{
"resource": "/exception",
"limitApp": "default",
"grade": 0,
"count": 200,
"slowRatioThreshold": 0.2,
"timeWindow": 10,
"minRequestAount": 5
},
{
"resource": "/exception",
"limitApp": "default",
"grade": 1,
"count": 0.3,
"timeWindow": 10,
"minRequestAount": 5
}
]

我们使用nacos来作为持久化容器。首先我们需要在配置文件中加入datasource部分。

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
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8818
# 是否支持懒加载
eager: true
datasource:
flow:
nacos:
#指定nacos服务器地址
server-addr: 127.0.0.1:8848
namespace: 77654658-9d96-4fdc-a69d-0f0ca7f9ad1a
dataId: ${spring.application.name}-${spring.profiles.active}-flow-rules
groupId: sentinel_rules
# 指定配置类型为流控
rule-type: flow
degrade:
nacos:
#指定nacos服务器地址
server-addr: 127.0.0.1:8848
namespace: 77654658-9d96-4fdc-a69d-0f0ca7f9ad1a
dataId: ${spring.application.name}-${spring.profiles.active}-degrade-rules
groupId: sentinel_rules
# 指定配置类型为熔断
rule-type: degrade

之后我们每次启动Sentinel之后,都会自动将Nacos中的配置直接同步到Sentinel控制台内。

Sentinel的实现原理

下图是官网的框架图。在设计上使用了责任链模式。对于规则的统计和处理相互独立,而且可以动态调整规则顺序和增删处理规则。首先外部请求进入后,会先经过8个槽(Slot),每个Slot会进行一些特定的处理。下面我针对每一个Slot来进行一些简单的说明。

arch overview

NodeSelectorSlot:这个Slot主要负责资源的调用路径,生成树状的调用链路信息。并且根据这些调用路径来限流降级,我们可以通过http://127.0.0.1:8719/tree?type=root来查看当前的调用链

以下是我的调用链,当作一个参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
EntranceNode: machine-root(t:0 pq:1.0 bq:0.0 tq:1.0 rt:2.0 prq:1.0 1mp:12 1mb:0 1mt:12)
-EntranceNode: sentinel_web_servlet_context(t:0 pq:1.0 bq:0.0 tq:1.0 rt:2.0 prq:1.0 1mp:12 1mb:0 1mt:12)
--/version(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/cluster/server_state/sora-auth(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/degrade/rules.json(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/registry/machine(t:0 pq:1.0 bq:0.0 tq:1.0 rt:2.0 prq:1.0 1mp:12 1mb:0 1mt:12)
--/paramFlow/rules(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/authority/rules(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/auth/login(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/app/briefinfos.json(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/system/rules.json(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/app/sora-gateway/machines.json(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/metric/queryTopResourceMetric.json(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/v1/flow/rules(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/app/sora-auth/machines.json(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/assets/img/sentinel-logo.png(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/v1/flow/rule(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
--/resource/machineResource.json(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)
-EntranceNode: sentinel_default_context(t:0 pq:0.0 bq:0.0 tq:0.0 rt:0.0 prq:0.0 1mp:0 1mb:0 1mt:0)


t:threadNum pq:passQps bq:blockQps tq:totalQps rt:averageRt prq: passRequestQps 1mp:1m-pass 1mb:1m-block 1mt:1m-total

ClusterBuilderSlot:用于构建集群节点以及调用来源节点,维护集群的各项指标

StatisticSlot:这是一个用于统计的Slot,用来记录资源的运行状况,比如成功的调用数、失败的调用数和响应时间等。并且Sentinel底层采用的是高性能滑动窗口数据结构,可以很好的统计实时数据,并且在高并发场景下依旧有很好的表现

sliding-window-leap-array

FlowSlot:流量控制的Slot,根据我们设置的规则来判断是否需要对规则进行限流。

DegradeSlot:熔断降级的Slot,主要在资源的表现不佳(根据资源的平均响应时间以及异常比率),对资源进行熔断降级

SystemSlot:系统保护的Slot,会在系统负载过高时,对部分资源进行保护,防止系统过载。