概述
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
还提供了其它额外的属性如 blockHandler
,blockHandlerClass
,fallback
用于表示限流或降级的操作(注意有方法签名要求),更多内容可以参考 Sentinel 注解支持文档。若不配置 blockHandler
、fallback
等函数,则被流控降级时方法会直接抛出对应的 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
| 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", "系统规则被触发..."); } 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> <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控制台,发现可以看流控规则。
访问接口,出现流控的提示。