spring_cloud_alibaba之sentinel使用

概述

https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel spring cloud alibaba sentinel

https://sentinelguard.io/zh-cn/docs/dashboard.html sentinel单独的地址,主要是脱离spring cloud 环境使用、架构说明等内容

sentinel以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

其中熔断降级与Hystrix的采用不同的方法。

Hystrix 通过线程池的方式,来对依赖(在我们的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。

Sentinel 对这个问题采取了两种手段:

  • 通过并发线程数进行限制

和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。

  • 通过响应时间对资源进行降级

除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。

根据版本对照

使用2022.0.0.0的pom依赖,使用Sentinel Version 为 1.8.6。

Sentinel 控制台

下载jar包:https://github.com/alibaba/Sentinel/releases

启动命令:

1
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.6.jar

登录:http://192.168.80.3:8080/

用户名密码:sentinel sentinel

简单使用

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
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
@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}

@Service
public class TestService {

@SentinelResource(value = "sayHello")
public String sayHello(String name) {
return "Hello, " + name;
}
}

@RestController
public class TestController {

@Autowired
private TestService service;

@GetMapping(value = "/hello/{name}")
public String apiHello(@PathVariable String name) {
return service.sayHello(name);
}
}

@SentinelResource 注解用来标识资源是否被限流、降级。上述例子上该注解的属性 sayHello 表示资源名。

@SentinelResource 还提供了其它额外的属性如 blockHandlerblockHandlerClassfallback 用于表示限流或降级的操作(注意有方法签名要求),更多内容可以参考 Sentinel 注解支持文档。若不配置 blockHandlerfallback 等函数,则被流控降级时方法会直接抛出对应的 BlockException;若方法未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException

配置文件配置控制台管理:bootstrap.properties

1
2
3
4
# 客户端连接至控制台的端口
spring.cloud.sentinel.transport.port=8720
# 控制台地址
spring.cloud.sentinel.transport.dashboard=192.168.80.3:8080

这里的 spring.cloud.sentinel.transport.port 端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了一个限流规则,会把规则数据 push 给这个 Http Server 接收,Http Server 再将规则注册到 Sentinel 中。

这时访问控制台地址,不会有项目在控制台中显示。

访问一下:http://localhost:8888/hello/sentinel 之后,等一会儿再访问sentinel发现项目。

也可以在配置文件中添加:

1
2
# 开启对sentinel看板的饥饿式加载。sentinel默认是懒加载机制,只有访问过一次的资源才会被监控,通过关闭懒加载,在项目启动时就连接sentinel控制台
spring.cloud.sentinel.eager = true

这时启动时就连接到控制台。

Feign使用sentinel

引入feign依赖、sentinel依赖

配置文件添加:

1
feign.sentinel.enabled=true

修改feign程序:

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
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(name = "service-consumer" , fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class)
public interface ExampleConfigFeign {

@RequestMapping("/config/get")
boolean get();

}

class FeignConfiguration {
@Bean
public EchoServiceFallback echoServiceFallback() {
return new EchoServiceFallback();
}
}

class EchoServiceFallback implements ExampleConfigFeign {
@Override
public boolean get() {
System.out.println("执行熔断降级方法");
return true;
}
}

以前程序访问:http://localhost:8888/echo2/aaaa 后会返回false。

这时我们把config/get改为config/get1,再次访问,会返回true。这样就实现了返回兜底数据,就是熔断降级,也可以返回异常。

流量控制

QPS

在控制台中,选择流控规则,新增流控规则,

资源名:/config/get1

阈值类型:QPS

单机阈值:2

流控模式:直接

流控效果:快速失败

保存后,访问http://localhost:8888/config/get1 多次访问出现 “Blocked by Sentinel (flow limiting)”,说明流控规则生效。

并发线程数

添加耗时方法:

1
2
3
4
5
6
7
8
9
@GetMapping(value = "/test")
public String test() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
System.out.println("睡眠被打断");
}
return "test";
}

新增流控规则:

资源名:/test

阈值类型:并发线程数

单机阈值:1

流控模式:直接

保存,启动项目,访问http://localhost:8888/test,多次刷新出现 “Blocked by Sentinel (flow limiting)”,说明流控规则生效。

并发线程数解析

并发数控制用于保护业务线程池不被慢调用耗尽 。

sentinel并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目) 。

如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。

流控模式

https://sentinelguard.io/zh-cn/docs/flow-control.html

直接模式

流控模式选择直接

就是对当前资源限流。

关联

资源名为 /test-b, 输入关联资源 /test-a, 当/test-a达到阈值后就限流/test-b。

高优先级资源触发阈值,对低优先级资源限流。

链路

资源名为/common, 入口资源为/test2。

阈值统计时,只统计从指定资源进入当前资源的请求,是对请求来源的限流。

流控效果

https://sentinelguard.io/zh-cn/docs/flow-control.html

快速失败

当QPS超过阈值后,新的请求就会被立即拒绝。

warm up

冷启动/预热,如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。

排队等待

严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法,主要用于处理间隔性突发的流量,如消息队列,想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

采用的是Leaky Bucket算法结合虚拟队列等待机制实现的。

暂时不支持QPS > 1000的场景。

熔断降级

https://sentinelguard.io/zh-cn/docs/circuit-breaking.html

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

慢调用比例

最大RT: 最大的响应时间

统计时长:统计的窗口有多大

比例阈值:就是统计时长内大于最大的响应时间的请求数/总请求数

最小请求数目:比如就统计时长内就5个请求,有3个请求超过最大响应时间,但是最小请求数目为5,还是不能进入熔断,样本不够。

熔断时长:就是进入熔断后,多长时间后再放一个请求去试,成功就结束熔断。

比如10秒内,最大RT 5秒, 超过80%, 最小请求数目为5个请求,熔断时长为30秒。

异常比例

统计时长:统计的窗口有多大

最小请求数目:比如就统计时长内就5个请求,有3个请求异常,但是最小请求数目为5,还是不能进入熔断,样本不够。

熔断时长:就是进入熔断后,多长时间后再放一个请求去试,成功就结束熔断。

比例阈值:就是统计时长内异常的请求数/总请求数

异常数

统计时长:统计的窗口有多大

最小请求数目:比如就统计时长内就5个请求,有3个请求异常,但是最小请求数目为5,还是不能进入熔断,样本不够。

熔断时长:就是进入熔断后,多长时间后再放一个请求去试,成功就结束熔断。

异常数:就是统计时长内异常请求数

服务返回自定义

流控时返回“Blocked by Sentinel (flow limiting)” ,http状态码为429。

这种服务之间是不好处理的。

sentinel的spring cloud alibaba包 2.1.0版本与2.2.0之后的版本,不向下兼容。

所以会有不同的实现方法。

2.1.0:

实现UrlBlockHandler并且重写blocked方法。

2.2.0:

实现BlockExceptionHandler并且重写handle方法。

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
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {

@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("code", 500);
if (e instanceof FlowException) {
map.put("msg", "流控规则被触发...");
} else if (e instanceof DegradeException) {
map.put("msg", "降级规则被触发...");
} else if (e instanceof ParamFlowException) {
map.put("msg", "授权规则被触发...");
} else if (e instanceof SystemBlockException) {
map.put("msg", "热点规则被触发...");
} else if (e instanceof AuthorityException) {
map.put("msg", "系统规则被触发...");
}
// 设置返回JSON数据
httpServletResponse.setStatus(200);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setHeader("content-Type", "application/json;charset=UTF-8");
httpServletResponse.setContentType("application/json;charset=utf-8");

httpServletResponse.getWriter().write(JSONObject.toJSONString(map));
}

}

这时调用将返回

1
{"msg":"流控规则被触发...","code":500}

动态规则扩展

前面的内容使用注解方式或者使用控制台,注解方式不灵活,控制台如果重启服务后配置的规则将会消失。

因为配置规则存在当前应用的内存中的,所以重启就消失了。

我们需要一种灵活的、持久化的方式。

https://sentinelguard.io/zh-cn/docs/dynamic-rule-configuration.html

Sentinel 提供两种方式修改规则:

  • 通过 API 直接修改 (loadRules)
  • 通过 DataSource 适配不同数据源修改

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

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

Sentinel 目前支持以下数据源扩展:

推模式:使用 Nacos 配置规则

https://sentinelguard.io/zh-cn/docs/dynamic-rule-configuration.html

https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>nacos-config</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacos-config</name>
<description>nacos-config</description>

<properties>
<spring-boot.version>3.0.2</spring-boot.version>
<spring-cloud.version>2022.0.0</spring-cloud.version>
<spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
<sentinel.version>1.8.6</sentinel.version>

<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
<!--Spring Cloud 新版本默认将 Bootstrap 禁用 ,开启bootstrap-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

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

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

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

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

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

</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</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>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>

主要是:

1
2
3
4
5
6
7
8
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

完整的配置文件为:

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
server.port=8888
spring.application.name=service-consumer

spring.cloud.nacos.config.server-addr=192.168.80.3:8848
spring.cloud.nacos.config.username=nacos
spring.cloud.nacos.config.password=nacos
spring.cloud.nacos.config.file-extension=json

spring.cloud.nacos.discovery.server-addr=192.168.80.3:8848
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos


feign.sentinel.enabled=true

spring.cloud.sentinel.transport.port=8720
spring.cloud.sentinel.transport.dashboard=192.168.80.3:8080

## 主要是这里
spring.cloud.sentinel.datasource.ds.nacos.server-addr=${spring.cloud.nacos.config.server-addr}
spring.cloud.sentinel.datasource.ds.nacos.data-id=${spring.application.name}-flow-rules
spring.cloud.sentinel.datasource.ds.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds.nacos.data-type=json
spring.cloud.sentinel.datasource.ds.nacos.rule-type=FLOW
spring.cloud.sentinel.datasource.ds.nacos.username=${spring.cloud.nacos.config.username}
spring.cloud.sentinel.datasource.ds.nacos.password=${spring.cloud.nacos.config.password}

其中rule-type 取值可以点进去看枚举,就是流控规则还是熔断规则等。

这个版本只相当于配置多个数据源,一个数据源只写一种rule-type。

nacos配置:

data-id: service-consumer-flow-rules

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

启动应用,查看sentinel的日志在用户目录/logs/csp目录下。

启动后无报错,去sentinel控制台,发现可以看流控规则。

访问接口,出现流控的提示。


spring_cloud_alibaba之sentinel使用
http://hanqichuan.com/2023/10/16/spring_cloud/spring_cloud_alibaba之sentinel使用/
作者
韩启川
发布于
2023年10月16日
许可协议