Spring Cloud Sentinel
1. Sentinel 简介
官方文档:
https://sentinelguard.io/zh-cn/docs/introduction.html
1.1 与Hystrix 的区别:
NO. | Hystrix | Sentinel |
---|---|---|
1 | 程序员自己手工搭建平台 | 单独一个组件,可以独立出来 |
2 | 没有一套web组件可以给我们进行更加细粒度化的配置。流量控制、速率控制、服务熔断、服务降级 | 界面化的细粒度统一配置 |
约定 > 配置 > 编码
Sentinel 的配置可以写在代码中,但是还是建议使用配置和注解的方式,少编码。
1.2 是什么?
阿里巴巴版的Hystrix。
如何使用: https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
1.3 主要特性:
2. Sentinel 安装与配置
2.1 下载
下载地址
https://github.com/alibaba/Sentinel/releases/tag/1.8.2 (1.8.2 版本)
sentinel组件由2部分组成
后台:
前台8080:
2.2 安装搭建
本次使用128 的机器安装 Sentinel
Linux安装配置JDK1.8
https://www.oracle.com/java/technologies/downloads/#java8
https://blog.youkuaiyun.com/pdsu161530247/article/details/81582980
https://www.cnblogs.com/lzw123-/p/9908748.html
https://www.cnblogs.com/imyalost/p/8745137.html
参考 Linux 搭建 Sentinel 方法:
https://blog.youkuaiyun.com/zhuocailing3390/article/details/123222875
128 的机器上 linux 启动脚本:
cd usr/local/sentinel
java -jar sentinel-dashboard-1.8.2.jar
或者: java -jar /usr/local/sentinel/sentinel-dashboard-1.8.2.jar
官方命令行:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
启动完成之后放开防火墙端口:
firewall-cmd --zone=public --add-port=8080/tcp --permanent
firewall-cmd --reload
附上 docker 启动脚本:128 机器
docker run -d -p 8088:8088 --name mySentinel bladex/sentinel-dsahboard
测试登录结果:
http://192.168.226.128:8080/#/dashboard
3. 新建调用sentinel的微服务
cloudalibaba-sentinel-service8403
3.1 pom
<dependencies>
<!-- SpringCloud Alibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- springcloud alibaba sentinel-datasource-nacos 后续作持久化用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- springcloud alibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.pyh.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
3.2 yaml
server:
port: 8403
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
# nacos 服务注册中心地址,需开启129 机器的 nginx + keepalived + nacos 集群
server-addr: 192.168.226.200:1111
sentinel:
transport:
# Sentinel dashboard 地址
dashboard: 192.168.226.128:8080
# 默认 8719 端口,假如被占用,会自动从8719开始依次+1扫描,直到找到未被占用的端口
port: 8719
client-ip: # Sentinel 安装在docker 上面时,需要添加该配置
management:
endpoints:
web:
exposure:
include: "*" # 暴露监控端点
3.3 主程序类
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelMainApp8403 {
public static void main(String[] args) {
SpringApplication.run(SentinelMainApp8403.class,args);
}
}
3.4 controller
@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA(){
return "------TestA";
}
@GetMapping("/testB")
public String testB(){
return "------TestB";
}
}
3.5 结果
Sentinel采用懒加载,所以即使启动了服务,还是没有看到任何东西
http://192.168.226.128:8080/
需要做的,是调用几次服务,再刷新
http://localhost:8403/testA
http://localhost:8403/testB
4. 功能-流控规则
功能列表:
具体的功能如下:
- 资源名: 唯一名称,默认请求路径
- 针对来源: Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
- 阈值类型/单机阈值:
* QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流
* 线程数: 当调用该api的线程数达到阈值的时候,进行限流
- 是否集群:这里没有集群
- 流控模式:
* 直接: api达到限流条件时,直接限流
* 关联:当关联的资源达到阈值时,就限流自己
* 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】
- 流控效果:
* 快速失败: 直接失败,抛异常
* Warm Up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值 排队等待:
* 匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效
4.1 流控规则
4.1.1 流控模式:直接
当访问频率超过上面设置的条件时(每秒1次),直接失败
另附:当阈值类型指定为 “线程数” 时,若单机阈值等于1,意思是同时只能允许一个线程在处理某个接口,超过一个时,如果前一个调用还未执行完毕,则报错。
4.1.2 流控模式:关联
说明: 当关联的资源达到阈值时,就限流自己
即: 当与A关联的资源B达到阈值后,就限流自己(B惹事,A挂了)
配置:
4.1.3 流控模式:链路
参考: https://www.freesion.com/article/96621141601/
从1.6.3 版本开始,Sentinel Web fifilter默认收敛所有URL的入口context,因此链路限流不生效。
1.7.0 版本开始(对应SCA的2.1.1.RELEASE),官方在CommonFilter 引入了WEB_CONTEXT_UNIFY 参数,用于控制是否收敛context。将其配置为 false 即可根据不同的URL 进行链路限流。
SCA 2.1.1.RELEASE之后的版本,可以通过配置spring.cloud.sentinel.web-context-unify=false即可关闭收敛我们当前使用的版本是SpringCloud Alibaba 2.1.0.RELEASE,无法实现链路限流。
目前官方还未发布SCA 2.1.2.RELEASE,所以我们只能使用2.1.1.RELEASE,需要写代码的形式实现
目前测试使用的是2.1.0版本,无法使用链路限流
4.2 流控效果
4.2.1 流控效果:直接->快速失败
默认的处理效果
4.2.2 流控效果:预热 warmup
说明:
公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
源码:
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
warmup 配置:
4.2.3 流控效果:排队等待
说明:
官网说明:
源码:
com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
5. 功能-熔断规则
官网:
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状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
5.1 熔断-慢调用熔断
熔断条件(下图):
在1000毫秒,也就是1秒内,如果发送到/testC 的请求数数量大于5,并且在这些请求中,所有请求的响应时长(因为比例与阈值为1,所以是所有的请求响应时长)都大于800毫秒的时候,进入熔断状态。
经过熔断时长5秒后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT(800毫秒)则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
本次测试使用Jmeter测试。
5.1.1 Sentinel 配置
5.1.2 controller
@GetMapping("/testC")
public String testC() throws InterruptedException {
Thread.sleep(1000);
return someService.doSth("testC");
}
5.1.2 Jmeter
5.2 熔断-异常比例熔断
5.2.1 规则配置
如图,在1000 毫秒的时间内,最小请求数达到5个,如果发生异常的比例达到0.5 或以上,则进入熔断状态。经过熔断时长(5秒)后熔断器会进入探测恢复状态(HALF-OPEN状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
5.2.2 controller
@GetMapping("/testC")
public String testC() throws InterruptedException {
int age = 10/0;
return someService.doSth("testC");
}
5.2.3 结果
正常调用
如5.2.1配置,调用数1秒时间内调用数量大于5,且异常数大于2个
5.3 熔断-异常数熔断
5.3.1 规则配置
如图,在时间窗口1000毫秒的时间内,最小请求数大于等于5的情况下,如果异常数量为5个,则进入熔断状态。经过熔断时长(5秒)后熔断器会进入探测恢复状态(HALF-OPEN状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
5.3.2 controller
与5.2.2 相同
5.3.3 结果
与5.2.3 相同
6. 功能-热点规则
6.1 官网
https://sentinelguard.io/zh-cn/docs/parameter-flow-control.html
6.2 热点规则跟 Hystrix比较
兜底方法:
分为系统默认和客户自定义两种。
之前的case,限流出问题后, 都是用sentinel系统默认的提示:Blocked by Sentinel(flow limiting)
而Sentinel,类似Hystrix,某个方法出问题了,就找对应的兜底降级方法:
结论:
从@HystrixCommand 到@SentinelResource
代码:
com.alibaba.csp.sentinel.slots.block.BlockException
6.3 配置热点规则
6.3.1 代码端
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
// value = "testHotKey" 资源名
// deal_testHotKey 兜底方法
public String testHotKey(@RequestParam(value = "p1", required = false) String p1
,@RequestParam(value = "p2", required = false) String p2){
return "----testHotKey:p1="+p1+";p2="+p2;
}
public String deal_testHotKey(String p1, String p2, BlockException e){
return "----deal_testHotKey show"; // Sentinel 系统默认的提示, Block by Sentinel (flow limiting)
}
代码端指定了Sentinel的规则资源名"testHotKey";
如果代码端不配置兜底方法 blockHandler,那么当外部调用违背了Sentinel配置的规则的时候,异常会直接返回给外部,不友好
6.3.2 Sentinel 界面端
配置解读:当资源名请求为testHotKey的调用,第一个参数(参数索引=0,即6.3.1的p1参数),每1秒的请求数量大于1时,则违背了Sentinel的热点规则,那么会调用代码配置的兜底方法,如果没有则直接抛出异常。
6.3.3 参数例外项
如6.3.2图,添加了一个参数值为5,类型为String,限流阈值为20的例外配置。
表示当p1=5时,限流阈值变为20,需要超过20才会触发Sentinel热点规则。
6.3.4 直接添加异常看看
public String testHotKey(@RequestParam(value = "p1", required = false) String p1
,@RequestParam(value = "p2", required = false) String p2){
int a = 10/0;
return "----testHotKey:p1="+p1+";p2="+p2;
}
@SentinelResource:
处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
@RuntimeException:
int a = 10/0,这个是java运行时报出的运行时异常RunTimeException, @SentinelResource不管
总结:
@SentinelResource 主管配置出错,运行出错该走异常走异常
7. 系统保护规则
7.1 官网:
如何使用:https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E7%B3%BB%E7%BB%9F%E4%BF%9D%E6%8A%A4%E8%A7%84%E5%88%99-systemrule
系统自适应限流: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
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
7.2 系统规则支持以下的模式:
Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
7.3 例子
如上图,即整个系统的所有入口的QPS 总和为1.
8. SentinelResource 配置
Sentinel也可以代码的方式来实现规则,如下官网:
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
但是一般不推荐
8.1 所需代码
8.1.1 pom
<!-- 引入自定义api通用包,可以使用payment支付entity -->
<dependency>
<groupId>com.pyh.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
8.1.2 controller
package com.pyh.springcloud.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.pyh.springcloud.entities.CommonResult;
import com.pyh.springcloud.entities.Payment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RateLimitController {
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "HandleException")
public CommonResult byResource(){
return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
}
public CommonResult HandleException(BlockException exception){
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用 ");
}
}
8.1.3 附api-common mudule
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
private Long id;
private String serial;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
// 404 not found
private Integer code;
private String message;
private T data;
// 上面配置了空参和全参, 这里自定义一个只有编码和消息的构造方法
public CommonResult(Integer code, String message){
this(code,message,null);
}
}
8.1.4 新增流控规则 byResource
记得需先调用一次该节点,使其显示在簇点链路上
此时关闭8403,发现流控规则消失了,说明目前Sentinel的流控规则是临时的
8.2 没有兜底方法
8.2.1 controller
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl(){
return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
}
8.2.2 Sentinel
8.2.3 结果:
没有自定义兜底方法,则返回Sentinel的默认的内容
8.3 客户自定义限流处理逻辑
8.3.1 前面配置带来的问题
1). 系统默认的,没有体现自己的业务要求
2). 依照现有条件,自定义的处理方法和业务代码耦合,不直观
3). 每个业务方法都添加兜底方法,则代码膨胀加剧
4). 全局统一的处理方法没有体现
8.3.2 定义全局兜底方法
新增 CustomerBlockHandler 类
package com.pyh.springcloud.myhandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.pyh.springcloud.entities.CommonResult;
public class CustomerBlockHandler {
public static CommonResult handlerException(BlockException exception){
return new CommonResult(444,"按客户自定义,global handlerException----1");
}
public static CommonResult handlerException2(BlockException exception){
return new CommonResult(444,"按客户自定义,global handlerException----2");
}
}
RateLimitController类 添加方法 customerBlockHandler
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler"
,blockHandlerClass = CustomerBlockHandler.class
,blockHandler = "handlerException2")
public CommonResult customerBlockHandler(){
return new CommonResult(200,"按客户自定义限流测试OK",new Payment(2020L,"serial003"));
}
配置 Sentinel 规则流控之后实现全局兜底方法控制