Sentinel 进阶教程 - 中级篇(2024 年)

大纲

前言

官方资源

版本说明

在本文的所有案例中,各个组件统一使用以下版本:

组件版本说明
Spring Boot3.2.0
Spring Cloud2023.0.0
Spring Cloud Alibaba2022.0.0.0
Sentinel Dashboard1.8.7

代码下载

  • 本文完整的案例代码可以从 这里 下载得到。

Sentinel 注解支持

@SentiinelResource 的概述

@SentiinelResource 是 Sentinel 官方提供的一个流量防卫防护组件注解,用于指定防护资源,对配置的资源进行流量控制、熔断降级等功能。@SentiinelResource 注解的底层源码和注释如下:

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
@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. 函数参数列表中可以多一个 BlockException 类型的参数
// 4. 默认需要和原函数处于同一个类中。若希望使用其他类的函数,可配置 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 类型的参数
Class<?>[] fallbackClass() default {};

// 需要 Trace 的异常
Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};

// 指定排除忽略掉哪些异常。排除的异常不会计入异常统计,也不会进入 fallback 逻辑,而是原样抛出异常
Class<? extends Throwable>[] exceptionsToIgnore() default {};

}

@SentiinelResource 的使用

默认不使用注解的行为表现

案例目标

Sentinel 使用接口的 URL 地址作为资源名进行限流 + 返回默认的限流提示。

  • Java 接口代码
1
2
3
4
5
6
7
8
9
@RestController
public class RateLimitController {

@GetMapping("/rateLimit/byUrl")
public String byUrl() {
return "按照 URL 地址进行限流测试";
}

}
  • Sentinel 控制台添加流控规则

  • 通过浏览器快速多次访问 /rateLimit/byUrl 接口时,会返回默认提示信息 Blocked by Sentinel (flow limiting),说明接口被限流了。

提示

在 Sentinel 控制台添加流控规则时,资源名默认就可以是请求的接口路径(URL),可以自行修改。同一微服务应用内,资源名必须唯一。不同微服务应用之间,资源名可以重复。

使用注解自定义限流的提示

案例目标

Sentinel 按照资源名称进行限流 + 返回自定义的限流提示。

若不希望 Sentinel 使用默认的限流提示 Blocked by Sentinel (flow limiting),而是想返回自定义限流的提示,可以使用 @SentiinelResource 注解 的 blockHandler 属性来实现。

  • Java 接口代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class RateLimitController {

/**
* @SentinelResource 注解的 blockHandler 属性用于指定处理 BlockException 异常的函数名称
*/
@GetMapping("/rateLimit/byResource")
@SentinelResource(value = "bySentinelResource", blockHandler = "handlerException")
public String byResource() {
return "按照资源名称进行限流测试";
}

public String handlerException(BlockException exception) {
return "目前访问人数较多,请稍后再试!";
}

}
  • Sentinel 控制台添加流控规则

  • 通过浏览器快速多次访问 /rateLimit/byResource 接口时,会返回自定义提示信息 目前访问人数较多,请稍后再试!,说明接口被限流了。

使用注解自定义降级处理逻辑

案例目标

Sentinel 按照资源名称进行限流 + 返回自定义的限流提示 + 服务降级处理(Fallback)。

使用 Sentinel 的时候,若希望在接口调用抛出业务异常(不包括 BlockException 异常)时,执行指定的降级处理逻辑,可以使用 @SentiinelResource 注解 的 fallback 属性来实现。特别注意,这里的降级处理只是在接口调用抛出业务异常时,简单执行指定的降级处理逻辑(比如返回兜底数据),并没有实现熔断的功能。若需要使用熔断功能,可以使用 Sentinel 提供的 熔断规则

  • Java 接口代码
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
@Slf4j
@RestController
public class RateLimitController {

/**
* @SentinelResource 注解的 blockHandler 属性用于指定处理 BlockException 异常的函数名称
* @SentinelResource 注解的 fallback 属性用于在接口抛出业务异常(不包括 BlockException)的时候提供 fallback 处理逻辑
*/
@GetMapping("/rateLimit/doAction/{p1}")
@SentinelResource(value = "doActionSentinelResource", blockHandler = "doActionBlockHandler", fallback = "doActionFallback")
public String doAction(@PathVariable("p1") Integer p1) {
if (p1 == 0) {
throw new RuntimeException("非法参数异常");
}
return "success";
}

public String doActionBlockHandler(@PathVariable("p1") Integer p1, BlockException e) {
log.error("接口触发了限流: {}", e);
return "目前访问人数较多,请稍后再试!";
}

public String doActionFallback(@PathVariable("p1") Integer p1, Throwable e) {
log.error("程序抛出了异常: {}", e);
return "服务出错啦,请稍后再试!";
}

}
  • Sentinel 控制台添加流控规则

  • 通过浏览器快速多次访问 /rateLimit/doAction/1 接口时,会返回自定义提示信息 目前访问人数较多,请稍后再试!,说明接口频繁调用时,成功被限流了。
  • 通过浏览器单次访问 /rateLimit/doAction/0 接口时,会返回自定义的降级处理结果 服务出错啦,请稍后再试!,说明调用接口抛出业务异常(不包括 BlockException)时,成功被降级处理了。

使用总结

  • @SentinelResource 注解的 blockHandler 属性,主要针对 Sentinel 配置了规则后出现的违规情况(比如触发了流控规则、熔断规则、热点规则等)进行处理。
  • @SentinelResource 注解的 fallback 属性,主要针对程序在运行时抛出了业务异常(不包括 BlockException 异常),需要进行降级处理的情况。
  • @SentinelResource 注解的 blockHandlerfallback 属性可以共存,也就是可以互相配合使用。

Sentinel 热点规则

热点规则

热点规则的概述

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

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

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

Sentinel 利用 LRU(最长时间未使用)策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。值得一提的是,热点参数限流支持集群模式。

LRU 算法与 LFU 算法

  • LRU (Least Recently Used):根据最近使用的顺序来淘汰数据,即淘汰最长时间未被访问的数据
  • LFU (Least Frequently Used):根据数据被访问的频率来淘汰数据,即淘汰访问频率最低的数据

热点规则的添加

在 Sentinel 控制台里,添加热点规则的步骤如下:

热点参数规则(ParamFlowRule)类似于流量控制规则(FlowRule),主要有以下配置项:

属性说明默认值
resource 资源名,必填
count 限流阈值,必填
grade 限流模式 QPS 模式
durationInSec 统计窗口时间长度(单位为秒),从 1.6.0 版本开始支持 1s
controlBehavior 流控效果(支持快速失败和匀速排队模式),从 1.6.0 版本开始支持快速失败
maxQueueingTimeMs 最大排队等待时长(仅在匀速排队模式生效),从 1.6.0 版本开始支持 0ms
paramIdx 热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置
paramFlowItemList 参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型
clusterMode 是否是集群参数流控规则 false
clusterConfig 集群流控相关配置

热点规则的使用

  • Java 接口代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@RestController
public class RateLimitController {

@GetMapping("/rateLimit/hotKey")
@SentinelResource(value = "testHotKey", blockHandler = "hotKeyHandler")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "success";
}

public String hotKeyHandler(String p1, String p2, BlockException exception) {
return "hot key rate limit";
}

}
  • Sentinel 控制台添加热点规则

特别注意

在 Sentinel 控制台添加热点规则时,资源名必须是 @SentinelResource 注解的 value 属性的值,不能是接口的 URL 地址,否则热点参数限流不会生效。

  • 测试案例代码
    • (1) 通过浏览器快速多次访问 /testHotKey?p1=abc 接口,发现会返回自定义的限流提示信息 hot key rate limit,说明热点参数限流生效。
    • (2) 通过浏览器快速多次访问 /testHotKey?p1=abc&p2=efg 接口,发现也会返回自定义的限流提示信息 hot key rate limit,说明只要请求含有指定的参数,热点参数限流都会生效。
    • (3) 通过浏览器快速多次访问 /testHotKey?p2=efg 接口,发现返回的结果都是 success,说明只要请求不含有指定的参数,即使不断访问接口也不会触发热点参数限流。

参数例外项配置

参数例外项的概述

在上述案例中,演示了接口携带第一个参数 p1,当 QPS 超过 1 秒 1 次时,接口马上被限流。但是,面对复杂易变的业务需求,往往会有特殊情况需要额外处理。

  • 正常限流
    • 接口携带第一个参数 p1,当 QPS 达到预设阀值时立刻被限流。
  • 特殊限流
    • 希望当 p1 参数是某个特殊值时,QPS 到达预设阀值后热点规则突然例外(失效),它的限流阀值跟平时不一样。
    • 比如:当 p1 参数的值等于 5 时,QPS 阀值可以允许达到 200 或者其他值。
参数例外项的使用
  • Java 接口代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@RestController
public class RateLimitController {

@GetMapping("/rateLimit/hotKey")
@SentinelResource(value = "testHotKey", blockHandler = "hotKeyHandler")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "success";
}

public String hotKeyHandler(String p1, String p2, BlockException exception) {
return "hot key rate limit";
}

}
  • Sentinel 控制台添加热点规则 + 参数例外项

  • 测试案例代码
    • (1) 通过浏览器快速多次访问 /testHotKey?p1=abc 接口,当 QPS 达到超过 1 秒 1 次时,发现会返回自定义的限流提示信息 hot key rate limit,说明普通的热点参数限流生效。
    • (2) 通过浏览器快速多次访问 /testHotKey?p1=mnt 接口,当 QPS 达到超过 1 秒 5 次时,才会返回自定义的限流提示信息 hot key rate limit,说明参数例外项的热点参数限流生效。

特别注意

使用参数例外项时,参数必须是基本类型或者 String 类型。

Sentinel 授权规则

授权规则的概述

在很多时候,往往需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 提供的授权规则(也叫黑白名单控制)来实现。授权规则根据资源的请求来源(Origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求都通过。

提示

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

授权规则的添加

在 Sentinel 控制台里,添加热点规则的步骤如下:

授权规则(AuthorityRule)非常简单,主要有以下配置项:

属性说明默认值
resource 资源名,即限流规则的作用对象。
limitApp 对应的黑名单 / 白名单,不同 origin 用 , 分隔,如 appA,appB
strategy 限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式。AUTHORITY_WHITE

授权规则的使用

  • Java 接口的代码
1
2
3
4
5
6
7
8
9
10
@Slf4j
@RestController
public class EmpowerController {

@GetMapping("/empower")
public String empower() {
return "success";
}

}
  • 自定义请求来源转换器,需要实现 RequestOriginParser 接口,目的是告知 Sentinel 从哪里获取请求来源
1
2
3
4
5
6
7
8
9
10
@Component
public class MyRequestOriginParser implements RequestOriginParser {

@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
// 指定从哪里获取请求来源,比如可以从 HTTP 请求的参数、Header 获取
return httpServletRequest.getParameter("serverName");
}

}
  • Sentinel 控制台添加授权规则

  • 测试案例代码
    • (1) 通过浏览器访问 /empower?serverName=appC 接口,会返回正确结果 success
    • (2) 通过浏览器访问 /empower?serverName=appA 接口,发现会返回 Blocked by Sentinel (flow limiting),说明授权规则生效了。
    • (3) 通过浏览器访问 /empower?serverName=appB 接口,发现会返回 Blocked by Sentinel (flow limiting),说明授权规则生效了。

Sentinel 配置规则持久化

由于一旦重启微服务应用,Sentinel 已配置的规则就会丢失,因此生产环境需要将 Sentinel 配置规则持久化。比如,将流控规则持久化进 Nacos 保存后,只要 Nacos 里面的配置信息不删除,Sentinel 的流控规则就会持续有效。值得一提的是,为了微服务重启后不丢失 Sentinel 已配置的规则,还可以使用 Sentinel 提供的 API 来自定义配置规则,也就是说通过代码的方式定义(写死) Sentinel 配置规则,比如 GatewayRuleManager.loadRules(rules),缺点是配置规则不能随意更改(不支持实时更新)。Sentinel 官方推荐注册动态规则源来实现动态推送,比如 GatewayRuleManager.register2Property (property)

特别注意

  • (1) Sentinel 网关流控规则的数据源类型是 gw-flow,若将网关流控规则的数据源类型指定为 flow 则不会生效,定义在 com.alibaba.cloud.sentinel.datasource.RuleType 枚举类中。
  • (2) 当 Sentinel 控制台从 Nacos 中加载完配置规则后,如果在 Sentinel 控制台修改配置规则,那么是不会将配置规则的更改同步到 Nacos 中的,也就是说配置规则的更改在下次微服务应用重启后会失效。正确的做法是在 Nacos 中更改 Sentinel 配置规则,因为 Sentinel 控制台会订阅 Nacos 中的配置规则变更,也就是 Sentinel 通过 Nacos 可以支持配置规则的实时更新。

引入依赖坐标

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

<!-- Sentinel 的 Nacos 数据源 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

提示

如果在项目中,已经使用了 Nacos 作为服务注册中心或者配置中心,则项目无需做任何改动,只需要正常引用上面的 Maven 依赖和添加下面的 YML 配置信息即可实现 Sentinel 配置规则持久化。

添加测试代码

1
2
3
4
5
6
7
8
9
10
@Slf4j
@RestController
public class RateLimitController {

@GetMapping("/rateLimit/byUrl")
public String byUrl() {
return "按 URL 地址进行限流测试";
}

}

添加 YML 配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
application:
name: sentinel-demo-service
cloud:
# Sentinel
sentinel:
transport:
dashboard: 192.168.56.112:8858 # 配置 Sentinel Dashboard 控制台的访问地址
port: 8719 # 默认 8719 端口,假如被占用会自动从 8719 开始依次 + 1 扫描,直至找到未被占用的端口
client-ip: 192.168.2.140 # 指定客户端的 IP
datasource:
ds1:
nacos:
server-addr: 192.168.56.112:8848
dataId: ${spring.application.name}
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow # com.alibaba.cloud.sentinel.datasource.RuleType

上面配置的 rule-type 参数的值来自 com.alibaba.cloud.sentinel.datasource.RuleType 枚举类,常用的值有以下几个:

上面配置的 datasource 可以有多个,可以理解为允许有多个不同类型的控制规则(如流量规则、熔断规则、热点规则等),如下图所示:

Nacos 添加流控规则

提示

这里只简单演示如何将 Sentinel 的流控规则配置信息持久化到 Nacos 中,如果是持久化熔断规则或者热点规则等其他配置信息,具体的 JSON 配置内容请自行查阅官方文档。

首先将 Sentinel 流控规则的配置信息转换为 JSON 配置内容:

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

Sentinel 流控规则的 JSON 配置内容说明:

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

然后将 Sentinel 流控规则的 JSON 配置内容添加到 Nacos 中:

最后在 Nacos 中 Sentinel 流控规则的配置信息如下:

案例代码测试

  • (1) 启动微服务应用。
  • (2) 通过浏览器访问微服务应用的 /rateLimit/byUrl 接口,等待几秒中,让 Sentinel 控制台发现微服务应用的存在。
  • (3) 通过浏览器访问 Sentinel 控制台的管理页面,点击 流控规则 菜单项,即可以看到在 Nacos 中添加的流控规则配置信息(如下图所示)。如果看不到对应的配置信息,可以多刷新几次页面试试。

  • (4) 通过浏览器快速多次访问 /rateLimit/byUrl 接口,当 QPS 达到超过 1 秒 1 次时,接口会返回默认提示信息 Blocked by Sentinel (flow limiting),说明 Sentinel 的流控规则生效了。
  • (5) 重启微服务应用,再次通过浏览器快速多次访问 /rateLimit/byUrl 接口,当 QPS 达到超过 1 秒 1 次时,接口仍然会被限流。