0%

Sentinel详解:服务熔断与限流

前言

在之前的文章”SpringCloudAlibaba微服务项目“中,我们搭建了一个demo,使用sentinel实现了简单的限流熔断,在本文中,我们进一步来学习sentinel的其他功能。

注意

  • 本文档针对 Sentinel 1.8.0 及以上版本。1.8.0 版本对熔断降级特性进行了全新的改进升级,请使用最新版本以更好地利用熔断降级的能力;

  • 我们使用子模块‘service-account’来演示;

  • 我们使用sentinel的push模式,将数据持久化到nacos中;但是我还是把sentinel设置界面贴出来,因为看着直观一些,实际我们不在sentinel页面设置规则,仅仅搭配sentinel-dashboard前端页面讲解;

  • sentinel的部署、‘service-account’项目的搭建、push模式的使用方法参考文章”Spring Cloud Alibaba微服务项目“中的相关条目

一、流控规则

1. 规则介绍

官方介绍:https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6

我们来看看流控规则的编辑界面:

image-20250204202806376

我们稍微解释一下这些选项:

1
2
3
4
5
6
7
8
9
10
11
12
- 资源名:一般是我们的请求路径
- 针对来源:来自于哪个应用
- 单机阙值:QPS(每秒处理的请求数)或线程数
- 是否集群:被请求的服务是否集群部署
- 流控模式:
- 直接:直接对当前资源进行控制
- 关联:当前资源(资源1)关联另一个资源(资源2),当资源2达到设定阙值,则限制资源1的访问
- 链路:针对特定的调用链路进行流量控制(通过链路模式,可以更精细地控制流量。例如,限制某个入口资源的流量,而不影响其他入口资源对同一目标资源的访问。)
- 流控效果:
- 快速失败:直接限制
- Warm Up:阙值不会一开始就达到设定值,而是在预热时间内逐步达到设定值
- 排队等待:在达到阙值后,新的请求会排队等待

这里有必要对WarmUp进一步说明:

在Sentinel的预热模式中,预热阶段允许的阈值百分比是通过一个冷启动公式动态计算的。这个公式基于令牌桶算法,并结合预热时长和当前时间,动态调整允许通过的请求量。

冷启动公式:允许的阈值 = 阈值 ×(1 -(预热时长-当前时间)/ (预热时长+冷启动因子×当前时间))

其中:

  • 阙值:设定的最大QPS(例如1000)
  • 预热时长:预热的总时间(例如10秒)
  • 冷启动因子:一个常数,用于控制预热曲线的斜率(英文为coldFactor,Sentinel中默认值为3,可以在配置文件中找到)
  • 当前时间:从预热开始到当前的时间(单位:秒)

这个公式可以简单理解为,随着当前时间增加,逐步提高允许的阈值

2. 规则演示
前置准备

​ 需要在项目‘service-account’的配置文件application.yml中添加数据源

1
2
3
4
5
6
7
8
9
datasource:
ds1:
nacos:
serverAddr: localhost:8848
#namespaces: namespacesId
groupId: SENTINEL_GROUP
dataId: service-account-flow-rules
dataType: json
ruleType: flow

在nacos中根据数据源的配置新建service-account-flow-rules.json文件

2.1 流控模式:直接

​ 原理:我们准备一个A资源,当A资源达到访问阙值时,sentinel会对它的访问进行限制

​ (1)添加代码

1
2
3
4
@GetMapping("/flowControlMode/doDirect")
public Result doDirect() {
return Result.ok().message("flow control mode : direct ");
}

​ (2)在sentinel页面添加流控规则,这里我们的配置,1秒内可以有10个请求通过(单机阙值);

​ 在service-account-flow-rules.json文件中添加配置:

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
// service-account-flow-rules
// 记得将注释删除,否则会报错
[
{
// 应用名称
"app": "service-account",
// 集群配置
"clusterConfig": {
"fallbackToLocalWhenFail": true,
"sampleCount": 10,
"strategy": 0,
"thresholdType": 0,
"windowIntervalMs": 1000
},
// 是否集群模式
"clusterMode": false,
// 流控效果;0,快速失败;1,Wram Up;2,排队等待
"controlBehavior": 0,
// 单机阈值
"count": 10,
// 创建者
"gmtCreate": 1738813463995,
// 创建时间
"gmtModified": 1738813463995,
// 阈值类型; 0,并发量限流;1,QPS流量控制
"grade": 1,
// id
"id": 2,
// ip
"ip": "26.26.26.1",
// 流控针对的调用来源,若为default的话,则不区分调用来源
"limitApp": "default",
// 端口号
"port": 8719,
// 资源名,即限流规则的作用对象
"resource": "/sentinelTest/flowControlMode/doDirect",
// 流控模式,调用关系限流策略; 0,直接;1,关联;2,链路
"strategy": 0
}
]

(3)使用JMeter进行压力测试,这里我配置了10个线程,在1秒内循环访问10次,即1秒内供访问100次;

​ 如图所示添加测试计划:

image-20250205182649935

image-20250205183132935

运行后我们查看结果:

image-20250205184200649

可以看到成功访问10次,后续的访问被限制

2.2 流控模式:关联

​ 原理:我们准备A和B两个资源,其中A为核心资源,B为关联资源;当B达到访问阙值时,sentinel会对A资源进行限制

​ (1)添加代码,我们在代码中添加两个接口

1
2
3
4
5
6
7
8
9
@GetMapping("/flowControlMode/doRelatedA")
public Result doRelatedA() {
return Result.ok().message("flow control mode : related A");
}

@GetMapping("/flowControlMode/doRelatedB")
public Result doRelatedb() {
return Result.ok().message("flow control mode : related B");
}

​ (2)在sentinel页面添加流控规则,可以看到我们设置的阙值依然为10,A资源和B资源关联,当B资源(关联资源)达到阙值时,A资源(核心资源)也会被限制;

image-20250205191610177

​ 在service-account-flow-rules.json文件中添加配置:

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
[
{
"app": "service-account",
"clusterConfig": {
"fallbackToLocalWhenFail": true,
"sampleCount": 10,
"strategy": 0,
"thresholdType": 0,
"windowIntervalMs": 1000
},
"clusterMode": false,
"controlBehavior": 0,
"count": 10,
"gmtCreate": 1738823604770,
"gmtModified": 1738823604770,
"grade": 1,
"id": 4,
"ip": "26.26.26.1",
"limitApp": "default",
"port": 8719,
"refResource": "/sentinelTest/flowControlMode/doRelatedB",
"resource": "/sentinelTest/flowControlMode/doRelatedA",
"strategy": 1
}
]

(3)我们在JMeter添加两个测试计划,针对B资源的测试计划配置10个线程,在1秒内循环访问10次;针对A资源的测试计划配置10个线程,但是1秒内只访问1次,另外配置调度器,让A资源的测试延迟1秒启动

image-20250206104608835

image-20250206103826417

A资源的测试计划

image-20250206104754125

image-20250206104653996

B资源的测试计划

我们分别启动两个测试计划,可以看到B资源的测试全部通过了,反而是A资源有很多失败,这正是因为B资源达到阙值后sentinel对A资源进行了保护

image-20250206105120234

B资源的压测结果

image-20250206105158477

A资源的压测结果
2.3 流控模式:链路

​ 原理:我们需要准备3个资源,common资源及A资源、B资源(入口资源);当我们调用A、B时都会在其中调用common资源,我们在sentinel中配置对A资源作为入口资源进行限制;这样同时调用A、B资源进行压力测试,会发现到达访问阙值时,A资源会被sentinel限制,而B资源可以全部通过

(1)添加代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** common资源 **/

public interface SentinelTestService {
/**
* 链路 公共方法
*/
void linkCommon();
}

@Slf4j
@Service
public class SentinelTestServiceImpl implements SentinelTestService {
@SentinelResource("linkCommon")
@Override
public void linkCommon() {
log.info("com.qiuli.account.service.impl.SentinelTestServiceImpl.linkCommon");
}
}
1
2
3
4
5
6
/** A资源 **/
@GetMapping("/sentinelTest/flowControlMode/doLinkA")
public Result doLinkA() {
sentinelTestService.linkCommon();
return Result.ok().message("flow control mode : link A");
}
1
2
3
4
5
6
/** B资源 **/
@GetMapping("/sentinelTest/flowControlMode/doLinkB")
public Result doLinkB() {
sentinelTestService.linkCommon();
return Result.ok().message("flow control mode : link B");
}

(2)添加配置文件

​ 在项目‘service-account’的配置文件中添加:

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

该配置项用于控制 Sentinel 是否将所有的 Web 请求收敛到同一个上下文中;设置为true时,将上下文收敛到一个文件,简化上下文管理,适合不需要区分调用来源的场景;设置为false时,Sentinel 会为每个 Web 请求创建独立的上下文,支持链路模式,适合需要精细化流量控制的场景

(3)在sentinel页面添加流控规则,可以看到我们对资源‘linkCommon’添加了入口资源为A资源的流控规则,阙值依然为10QPS

image-20250206153400320

​ 在service-account-flow-rules.json文件中添加配置:

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
[
{
"app": "service-account",
"clusterConfig": {
"fallbackToLocalWhenFail": true,
"sampleCount": 10,
"strategy": 0,
"thresholdType": 0,
"windowIntervalMs": 1000
},
"clusterMode": false,
"controlBehavior": 0,
"count": 10,
"gmtCreate": 1738824027262,
"gmtModified": 1738824027262,
"grade": 1,
"id": 5,
"ip": "26.26.26.1",
"limitApp": "default",
"port": 8719,
"refResource": "/sentinelTest/flowControlMode/doLinkA",
"resource": "linkCommon",
"strategy": 2
}
]

(4)进行压力测试

​ 我们分别添加两个测试计划,对A资源和B资源进行压力测试,都是10个线程在1秒内循环10次,结果为:

image-20250212115651644

A资源压测计划

image-20250206154552985

A资源压测结果

image-20250212115803988

B资源压测计划

image-20250206154638875

B资源压测结果

​ 可以看到,同样的压测力度B资源全部通过,而A资源在达到在sentinel中设置的流控规则的阙值时,sentinel就限制了其访问

(5)添加后置处理

​ 我们可以看到,sentinel对A资源进行限制时,抛出了错误;我们可以增加注解@SentinelResource中的 blockHandler 属性以及方法,这样我们可以获得一个相对友好的处理结果;我们修改代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@Service
public class SentinelTestServiceImpl implements SentinelTestService {
@SentinelResource(value = "linkCommon", blockHandler = "blockHandlerForLinkCommon")
@Override
public boolean linkCommon() {
log.info("com.qiuli.account.service.impl.SentinelTestServiceImpl.linkCommon");
return true;
}

public boolean blockHandlerForLinkCommon(BlockException blockException) { log.info("com.qiuli.account.service.impl.SentinelTestServiceImpl.blockHandlerForLinkCommon");
return false;
}
}

​ 这里需要说明的是,blockHandler标记的方法的返回值类型需要和资源本身的返回值类型相同

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
/**
* 资源A
*
* @return
*/
@GetMapping("/flowControlMode/doLinkA")
public Result doLinkA() {
boolean flag = sentinelTestService.linkCommon();
if (flag) {
return Result.ok().message("flow control mode : link A");
} else {
return Result.error().message("flow control mode : link A; linkCommon资源受到sentinel流控");
}
}

/**
* 资源B
*
* @return
*/
@GetMapping("/flowControlMode/doLinkB")
public Result doLinkB() {
boolean flag = sentinelTestService.linkCommon();
if (flag) {
return Result.ok().message("flow control mode : link B");
} else {
return Result.error().message("flow control mode : link B; linkCommon资源受到sentinel流控");
}
}

​ 我们再次对A资源进行压测,观察结果:

image-20250206172030126

可以看到现在受到流控后不是直接抛出错误,而是由我们自定义的方法处理返回结果

二、降级规则

1. 规则介绍

官方介绍:https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7

概述

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。

chain

现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。

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

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

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

  • 触发熔断的最小请求数目,若当前统计窗口内的请求数小于此值,即使达到熔断条件规则也不会触发。

  • 异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex) 记录业务异常。开源整合模块,如 Sentinel Dubbo Adapter, Sentinel Web Servlet Filter 或 @SentinelResource 注解会自动统计业务异常,无需手动调用。

2. 规则演示
前置准备

需要在‘service-account’模块的配置文件application.yml中添加数据源

1
2
3
4
5
6
7
8
9
10
11
12
datasource:
ds2:
nacos:
serverAddr: localhost:8848
#namespaces: namespacesId
groupId: SENTINEL_GROUP
# 配置文件名称
dataId: service-account-degrade-rules
# 数据类型
dataType: json
# 规则类型 降级
ruleType: degrade

在nacos中根据数据源的配置新建配置文件service-account-degrade-rules.json

我们来看看‘降级规则’-‘慢调用比例’的调用界面

image-20250209145507951

1
2
3
4
5
资源名:请求路径
最大RT: 最大响应时间,如果请求的时间超过这个时间值,这次请求就是一个慢调用
比例阙值:慢调用次数占总调用次数的阙值(超过这个阙值将会对该资源进行熔断)
熔断时长:顾名思义
最小请求数:在统计时间内调用该资源的最小次数
2.1 慢调用比例

(1)我们在‘service-account’服务中添加一个测试接口‘/sentinelTest/reduce/slowRequestRatio’,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 降级 慢调用比例
* 0: 正常
* 1:超时
* 2:出错
*
* @return
*/
@GetMapping("/sentinelTest/reduce/slowRequestRatio")
public Result slowRequestRatio(@RequestParam String type) {
if ("0".equals(type)) {
return Result.ok().message("slow request ratio, type : " + type);
} else if ("1".equals(type)) {
// 超时,睡眠1000毫秒
try {
Thread.sleep(1000);
} catch (Exception e) {
log.info(e.getMessage());
}
}
return Result.ok().message("slow request ratio, type : " + type);
}

(2)在service-account-degrade-rules.json文件中添加配置

image-20250210173222626

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用时将注释全部删除,否则会报错
[
{
// 资源名称
"resource": "/sentinelTest/reduce/slowRequestRatio",
// 熔断策略 0,慢调用比例;1,异常比例;2,异常数
"grade": 0,
// ‘慢调用比例’时为‘最大RT’(单位为毫秒);
"count": 500,
// 比例阙值
"slowRatioThreshold": 0.1,
// 熔断时长
"timeWindow": 5,
// 最小请求数
"minRequestAmount": 5
}
]

(3)进行压力测试

​ 从上面的代码和配置可以看到,当我们输入参数‘0’时,程序立即返回结果;当我们输入参数‘1’时,程序休眠1000毫秒后再返回结果,而我们配置的最大RT为500毫秒,这样请求就成为慢调用;测试时我们可以先传递参数‘1’,让接口的慢调用比例超过设定值,再传递参数‘0’,让接口正常调用;

​ 我们设置两个线程组,一个是慢调用组,10个线程各循环1次;一个是正常调用组,10个线程各循环1次,并且正常调用组延迟1秒启动;

image-20250212115908879

慢调用压测计划

image-20250210175605500

慢调用压测结果

image-20250212120043510

正常调用压测计划

image-20250210175632354

正常调用压测结果

可以看到,因为慢调用比例迅速上升,所以正常调用服务也被限流了;等过了设置的’熔断时长‘再调用,服务就会恢复

2.2 异常比例

(1)添加代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 降级 异常比例
* 0: 正常
* 1:异常
*
* @param type 类型
* @return 结果
*/
@GetMapping("/reduce/errorRatio")
public Result errorRatio(@RequestParam String type) {
if ("0".equals(type)) {
return Result.ok().message("error ratio, type : " + type);
} else if ("1".equals(type)) {
int i = 1 / 0; // 这里会抛出 ArithmeticException
}
return Result.ok().message("error ratio, type : " + type);
}

(2)在配置文件service-account-degrade-rules.json中添加相应降级规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用时将注释全部删除
[
{
//资源名
"resource": "/sentinelTest/reduce/errorRatio",
//熔断策略 - 异常比例
"grade": 1,
// ‘异常比例’时为‘比例阈值’
"count": 0.1,
//熔断时长
"timeWindow": 5,
//最小请求数
"minRequestAmount": 5
}
]

(3)进行压力测试

​ 从上面的代码和配置可以看出,当异常比例大于0.1时,sentinel会对正常访问限流;传递参数‘0’时为正常调用,传递参数‘1’时为异常调用;

​ 我们在jmeter中设置两个线程组,一个负责正常调用,一个负责异常调用;正常调用线程组为10个线程,每个线程循环10次;异常调用线程组为10个线程,每个线程循环2次;

image-20250212142947490

异常调用组

image-20250211113819775

异常调用组结果

image-20250212144133039

正常调用组

image-20250211114015360

正常调用组结果

从结果可以看到,因为异常调用为20次,正常调用为100次,异常调用比例已经超过设置的0.1阙值,所以正常调用也被sentinel限流;等过了设置的’熔断时长‘再调用,服务就会恢复

2.3 异常数

(1)添加代码(这里虽然可以用上面的接口,但是为了更好测试,还是添加一个新接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 降级 异常数
* 0:正常
* 1:异常
*
* @param type 类型
* @return 结果
*/
@GetMapping("/reduce/errorCount")
public Result errorCount(@RequestParam String type) {
if ("0".equals(type)) {
return Result.ok().message("error count, type : " + type);
} else if ("1".equals(type)) {
int i = 1 / 0; // 这里会抛出 ArithmeticException
}
return Result.ok().message("error count, type : " + type);
}

(2)在配置文件service-account-degrade-rules.json中添加相应降级规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用时将注释全部删除
[
{
//资源名
"resource":"/sentinelTest/reduce/errorCount",
//熔断策略 - 异常数
"grade":2,
//异常数
"count":2,
//熔断时长
"timeWindow":5,
//最小请求数
"minRequestAmount":5
}
]

(3)进行压力测试

​ 从代码和配置,我们看到如果异常数超过2次,就会触发降级;传递参数‘0’时为正常调用,传递参数‘1’时为异常调用;

​ 我们在jmeter中设置两个线程组,一个负责正常调用,一个负责异常调用;正常调用线程组为10个线程,每个线程循环10次;异常调用线程组为5个线程,每个线程循环1次;让正常调用线程组延迟1秒启动

image-20250212144329078

异常组调用

image-20250211134530316

异常组调用结果

image-20250212144432148

正常调用

image-20250211140146058

正常组调用结果

​ 从结果可以看到,当异常调用的次数超过设置的阙值,正常调用也会被sentinel限流;等过了设置的’熔断时长‘再调用,服务就会恢复

三、热点参数规则

1. 规则介绍
官方介绍

网址:https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81

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

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

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

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。

补充

LRU策略(Least Recently Used,最近最少使用):LRU 是一种缓存淘汰策略,常用于管理有限资源(如缓存空间)。它的核心思想是:优先淘汰最近最少使用的数据

令牌桶算法(Token Bucket Algorithm):令牌桶算法是一种常用的流量控制算法,用于限制系统的请求速率。它的核心思想是:以固定的速率生成令牌,请求需要消耗令牌才能被处理

2. 规则演示
注意

热点规则需要使用@SentinelResource(“app”)注解,否则不生效,并且参数必须是7种基本数据类型才会生效。

前置准备

需要在‘service-account’模块的配置文件application.yml中添加数据源

1
2
3
4
5
6
7
8
9
10
11
12
datasource:
ds3:
nacos:
serverAddr: localhost:8848
#namespaces: namespacesId
groupId: SENTINEL_GROUP
# 配置文件名称
dataId: service-account-param-flow-rules
# 数据类型
dataType: json
# 规则类型 热点参数
ruleType: param-flow

在nacos中根据数据源的配置新建service-account-param-flow-rules.json文件;

我们看看热点参数的配置页面

image-20250211161357976

1
2
3
4
5
6
7
8
9
10
资源名:请求路径
参数索引:第几个参数
单机阙值:全局默认限流阈值,适用于所有未单独配置的参数值
统计窗口时长:
是否集群:

# 参数例外项
参数类型:
参数值:
限流阙值:针对特定参数值的个性化限流阈值,优先级更高
2.1 热点参数限流

(1)添加代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 热点参数限流
*
* @param type
* @return
*/
@GetMapping("/hotParam")
@SentinelResource(value = "hotParam", blockHandler = "blockHandlerForHotParam")
public Result hotParam(@RequestParam String type) {
return Result.ok().message("hotParam, type : " + type);
}

/**
* 热点参数限流 后置处理方法
*
* @param type
* @param be
* @return
*/
public Result blockHandlerForHotParam(String type, BlockException be) {
return Result.error().message("参数被限流,参数值:" + type);
}

(2)在配置文件service-account-param-flow-rules.json中添加规则

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
// 使用时将注释删除
[
{
// 资源名称
"resource": "hotParam",
// 限流模式 1=QPS
"grade": 1,
// 参数索引
"paramIdx": 1,
// 资源全局限流阙值
"count": 100,
// 统计窗口时长
"durationInSec": 5,
// 参数配置集合
"paramFlowItemList": [
{
// 参数类型
"classType": "java.lang.String",
// 参数值
"object": "0",
// 参数限流阙值
"count": 10
}
],
// 是否集群
"clusterMode": false
}
]

​ 添加规则后的sentinel页面

image-20250212144717683

(3)压力测试

​ 根据代码和配置的规则,我们可以看到,我们对资源全局限流阙值为10(每秒最多 100 次请求),如果参数值为’0‘,则限流阙值为1(每秒最多 10 次请求);

​ 我们先对全局限流进行测试,在jmeter中新建线程组,设置20个线程在1秒内循环访问10次,参数为’1‘(避开热点参数)

image-20250212114711979

资源全局限流测试设置

image-20250212114431462

资源全局限流测试结果

​ 可以看到,因为我们在代码中设置了后置处理方法,所以没有报错,但在超过阙值后还是被限流了;

​ 我们再对热点参数限流进行测试,我们新建线程组,设置10个线程在1秒内循环访问2次,参数为’0‘(用热点参数访问)

image-20250212115140660

热点参数访问

image-20250212115224786

热点参数访问结果

可以看到,用热点参数访问,超过了设置的阙值(每秒10次),所以虽然只进行了20次访问,也被限流了

四、授权规则(黑白名单)

1. 规则介绍

官方介绍:https://github.com/alibaba/Sentinel/wiki/%E9%BB%91%E7%99%BD%E5%90%8D%E5%8D%95%E6%8E%A7%E5%88%B6

很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

调用方信息通过 ContextUtil.enter(resourceName, origin) 方法中的 origin 参数传入。

2. 规则演示
前置准备

需要在‘service-account’模块的配置文件application.yml中添加数据源

1
2
3
4
5
6
7
8
ds4:
nacos:
serverAddr: localhost:8848
#namespaces: 49b762ac-54b1-4aff-b17f-6e6a32cfe5b8
groupId: SENTINEL_GROUP
dataId: service-account-authority-rules
dataType: json
ruleType: authority

在nacos中根据数据源新建service-account-authority-rules.json文件;

我们看看sentinel配置页面

image-20250212172715287

1
2
3
资源名:请求路径
流控应用:调用该资源的调用方,它需要用一个参数来表明自己的身份
授权类型:白名单:除了配置的调用方别人不许调用;黑名单:配置的调用方不许调用,别人可以调用

在代码中添加来源解析器:

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class AccountRequestOriginParser implements RequestOriginParser {

@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String origin = httpServletRequest.getParameter("origin");
if (origin == null) {
origin = "defaultOrigin";
}
return origin;
}
}

这个来源解析器从请求中获取参数’origin‘的值,用于进行黑白名单判断

2.1 白名单

(1)添加代码

​ 为了测试不互相干扰,我们还是新建一个接口

1
2
3
4
5
6
7
8
9
/**
* 授权规则(黑白名单)
*
* @return
*/
@GetMapping("/auth/whiteList")
public Result authWhiteList() {
return Result.ok().message("auth whiteList");
}

(2)在service-account-authority-rules.json文件中添加规则:

1
2
3
4
5
6
7
8
9
10
11
// 使用时将注释删除
[
{
// 资源名称
"resource": "/sentinelTest/auth/whiteList",
// 来源名称(可以有多个来源,用“,”分割,例如:“appA,appB”)
"limitApp": "app",
// 规则类型 0,白名单;1,黑名单
"strategy": 0
}
]

刷新sentinel页面,可以看到规则已经加载到sentinel了

image-20250212175617982

(3)测试,利用postman访问

image-20250212175711343

成功结果

image-20250212175743389

失败结果1

image-20250212175821438

失败结果2

从测试结果可以看到,我们按照配置的白名单规则传递参数’origin=app‘时,通过了sentinel的拦截成功访问,而当我们传递的参数错误或没有传递参数时,访问被sentinel拦截了

2.2 黑名单

(1)添加代码

1
2
3
4
5
6
7
8
9
/**
* 授权规则(黑名单)
*
* @return
*/
@GetMapping("/auth/blockList")
public Result authBlackList() {
return Result.ok().message("auth blockList");
}

(2)配置规则

​ 在service-account-authority-rules.json文件中添加规则:

1
2
3
4
5
6
7
8
9
10
11
// 使用时将注释删除
[
{
// 资源名
"resource": "/sentinelTest/auth/blockList",
// 来源名称(可以有多个来源,用“,”分割,例如:“appA,appB”)
"limitApp": "app",
// 规则类型 0,白名单;1,黑名单
"strategy": 1
}
]

​ 刷新sentinel页面,看到规则已经加载到sentinel

image-20250212193313636

(3)测试,利用postman访问

image-20250212193428095

拦截成功

image-20250212193515285

访问来源避开黑名单,访问成功

可以看到,当我们用sentinel中配置的黑名单上的访问源(origin=app)访问时,访问被sentinel限制了;用其他的访问源时,可以成功访问

五、系统规则

1. 规则介绍

官方介绍:https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、总体平均 RT、并发线程数、入口 QPS 、CPU 使用率等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统规则支持以下的模式:

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

在项目’service-account‘的配置文件中添加数据源

1
2
3
4
5
6
7
8
ds5:
nacos:
serverAddr: localhost:8848
#namespaces: 49b762ac-54b1-4aff-b17f-6e6a32cfe5b8
groupId: SENTINEL_GROUP
dataId: service-account-system-rules
dataType: json
ruleType: system

根据数据源在nacos中新建service-account-system-rules.json,在其中添加规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用时删除注释
[
{
// Load 自适应(我的cpu有16个逻辑核心)
"highestSystemLoad": 8,
// CPU usage(CPU利用率)
"highestCpuUsage": 0.5,
// 入口QPS
"qps": 100,
// 平均RT
"avgRt": 500,
// 最大线程数(并发线程数)
"maxThread": 10
}
]

我们来看看sentinel系统规则的配置页面

image-20250213115040516

可以看到sentinel已经成功载入了我们配置的系统规则,由于’系统load‘在Windows无法生效,我们接下来测试’平均RT‘、’并发数‘、’入口QPS‘

2.1 平均RT

(1)添加代码

​ 添加一个新接口,让其休眠1000毫秒,使其访问时间超过设置的平均RT(500毫秒)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 系统规则 平均RT
*
* @return
*/
@GetMapping("/system/avgRt")
public Result avgRt() {
try {
Thread.sleep(1000);
} catch (Exception e) {
log.info(e.getMessage());
}
return Result.ok().message("system avgRt");
}

(2)设置规则,我们已经完成了’平均RT‘的设置

(3)压力测试

​ 设置一个线程组,线程数为10,循环10次,对接口’/system/avgRt‘进行压力测试

image-20250213132850683

平局RT压力测试

image-20250213133023671

测试结果树

image-20250213133106556

聚合报告

可以从测试结果看出,因为我们的代码中让请求休眠1000毫秒,远超过我们设置的系统规则’平均RT‘500毫秒,所以异常超过97%,大多数请求都被sentinel限流

(4)修改代码,进行对照测试

​ 我们在代码中,将休眠时间修改为100毫秒,再次进行压力测试

image-20250213143344289

聚合报告

可以看到,当平均RT小于设定的500毫秒时,压力测试不会产生异常

2.2 并发数

(1)添加代码

1
2
3
4
5
6
7
8
9
/**
* 系统规则 maxThread
*
* @return
*/
@GetMapping("/system/maxThread")
public Result maxThread() {
return Result.ok().message("system maxThread");
}

(2)已经添加了配置规则,最大线程数为10

(3)压力测试

​ 先设置一个线程组,线程数为10,循环10次,对接口’/system/maxThread‘进行压力测试

image-20250213140204526

10线程测试

image-20250213140305421

测试结果

可以看到,我们设置了10个线程的线程组进行压力测试,没有超过我们在sentinel中设置的最大线程数10,所以异常为0%,sentinel没有限制访问;

接着我们设置一个20线程的线程组,同样循环访问10次

image-20250213140634657

20线程测试

image-20250213140734202

聚合报告

可以看到,当我们用超过sentinel中系统规则最大线程数的20线程测试,开始出现了异常,异常率为29.5%,说明sentinel对访问进行了限流

2.3 入口 QPS

(1)添加代码

1
2
3
4
5
6
7
8
9
/**
* 系统规则 qps
*
* @return
*/
@GetMapping("/system/qps")
public Result qps() {
return Result.ok().message("system qps");
}

(2)配置规则

​ 已经添加了配置规则,qps为100

(3)压力测试

​ 我们先设置一个线程组,线程数为10,1秒内循环访问10次,对接口’/system/qps‘进行压力测试

image-20250213142347546

10线程10循环测试

image-20250213142451225

聚合报告

可以看到,当qps没有超过配置阙值时,压力测试中没有异常;

我们修改线程组,为10线程,1秒内循环访问20次,这样qps来到了200,再次进行压力测试

image-20250213142752702

10线程20循环测试

image-20250213142914438

聚合报告

可以看到,当qps超过设定阙值,出现了异常,异常率为29.5%