Sentinel
sentinel分布式系统的流量防卫兵,是阿里巴巴开源的,面向分布式服务架构的高可用防护组件。
主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
源码地址: https://github.com/alibaba/Sentinel
官方文档: https://github.com/alibaba/Sentinel/wiki
Sentinel具有以下特征:
- 丰富的应用场景:Sentinel承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控 制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
- 完备的实时监控: Sentinel同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据, 甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、 gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。
- 完善的 SPI 扩展点:Sentinel提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。 例如定制规则管理、适配数据源等。
Sentinel初体验(源生方式)
Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。
此部分主要介绍 Sentinel 核心库的使用。如果希望有一个最快最直接的了解。
我们说的资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。使用 Sentinel 来进行资源保护,主要分为几个步骤:
- 定义资源
- 定义规则
- 检验规则是否生效
先把可能需要保护的资源定义好(埋点),之后再配置规则。也可以理解为,只要有了资源,我们就可以在任何时候灵活地定义各种流量控制规则。在编码的时候,只需要考虑这个代码是否需要保护,如果需要保护,就将之定义为一个资源。
对于主流的框架,我们提供适配,只需要按照适配中的说明配置,Sentinel 就会默认定义提供的服务,方法等为资源。
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--sentinel核心库-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!--如果需要使用@SentinelResource-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.0</version>
</dependency>
</dependencies>
流控体验(代码的方式,很少使用)
@RestController
@Slf4j
public class HelloController {
private static final String RESOURCE_NAME = "hello";
private static final String USER_RESOURCE_NAME = "user";
private static final String DEGRADE_RESOURCE_NAME = "degrade";
// 进行sentinel流控
@RequestMapping("/hello")
public String hello(){
Entry entry = null;
try {
// sentinel针对资源进行限制
entry = SphU.entry(RESOURCE_NAME);
String str = "hello sentinel";
log.info("+++++++++" + str + "+++++++++");
return str;
} catch (BlockException be){
//资源访问阻止,被限流或被降级
//进行相应的处理操作
log.info("block!");
return "被流控了";
} catch (Exception e) {
// 若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(e, entry);
}finally {
if(entry != null){
entry.exit();
}
}
return null;
}
/**
* 流控规则
*/
@PostConstruct
private static void initFlowRules(){
// 流控规则
List<FlowRule> rules = new ArrayList<>();
// 流控
FlowRule rule = new FlowRule();
// 设置被保护的资源
rule.setResource(RESOURCE_NAME);
// 设置流控规则的模式
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
rule.setCount(1);
rules.add(rule);
// 加载配置好的规则
FlowRuleManager.loadRules(rules);
}
}
流控体验(注解的方式)
@RestController
@Slf4j
public class HelloController {
private static final String RESOURCE_NAME = "hello";
private static final String USER_RESOURCE_NAME = "user";
private static final String DEGRADE_RESOURCE_NAME = "degrade";
/**
* @ SentinelResource 改善接口中资源定义和被流控降级后的处理方法
*
* 使用步骤:①添加依赖 sentinel-annotation-aspectj
* ②配置bean SentinelResourceAspect
*用法:
* value : 定义资源
* blockHandler : 设置流控降级后的处理方法
* (默认该方法必须声明在同一个类中,如果不在一个类中可以通过blockHandlerClassZ指定,但方法就需要是static修饰)
* fallback : 当接口出现了异常,可以交给fallback指定的方法来处理
* blockHandler的优先级高于fallback
* @param id
* @return
*/
@RequestMapping("/user")
@SentinelResource(value = USER_RESOURCE_NAME,fallback = "fallbackForGetUser", blockHandler = "blockHandlerForGetUser")
public User getUser(String id){
// int i = 1/0;
return new User("kiwi");
}
/**
*
* 注意:①一定要是public
* ②返回值一定要和源方法保存一致,包含源方法的参数
* ③参数的最后添加一个BlockException,可以区分是什么类型的规则处理方法
*
* @param id
* @param be
* @return
*/
public User blockHandlerForGetUser(String id, BlockException be){
be.printStackTrace();
return new User("流控!");
}
public User fallbackForGetUser(String id, Throwable e){
e.printStackTrace();
return new User("异常了");
}
/**
* 流控规则
*/
@PostConstruct
private static void initFlowRules(){
// 流控规则
List<FlowRule> rules = new ArrayList<>();
// 流控
FlowRule rule = new FlowRule();
// 设置被保护的资源
rule.setResource(RESOURCE_NAME);
// 设置流控规则的模式
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
rule.setCount(1);
rules.add(rule);
// 流控
FlowRule rule2 = new FlowRule();
// 设置被保护的资源
rule2.setResource(USER_RESOURCE_NAME);
// 设置流控规则的模式
rule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
rule2.setCount(1);
rules.add(rule2);
// 加载配置好的规则
FlowRuleManager.loadRules(rules);
}
}
降级体验(注解的方式)
@RestController
@Slf4j
public class HelloController {
private static final String RESOURCE_NAME = "hello";
private static final String USER_RESOURCE_NAME = "user";
private static final String DEGRADE_RESOURCE_NAME = "degrade";
@RequestMapping("/degrade")
@SentinelResource(value = DEGRADE_RESOURCE_NAME, blockHandler = "blockHandlerForDegrade")
public User degrade(String id){
throw new RuntimeException("手动异常");
}
public User blockHandlerForDegrade(String id, BlockException blockException){
return new User("降级了");
}
/**
* 降级规则
*/
@PostConstruct
private static void initDegradeRule(){
ArrayList<DegradeRule> degradeRules = new ArrayList<>();
DegradeRule degradeRule = new DegradeRule();
degradeRule.setResource(DEGRADE_RESOURCE_NAME);
// 设置规则策略:异常数
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
// 下面四个参数的意思是:一分钟内,执行了2次出现2次异常就会触发熔断
// 异常数:2
degradeRule.setCount(2);
// 统计时长
degradeRule.setStatIntervalMs(60*1000);
// 触发熔断最小请求数:2
degradeRule.setMinRequestAmount(2);
// 熔断时长:单位是s
// 一旦触发了熔断,再次请求对应的接口就会直接调用降级的方法
// 超过了时间窗口之后就进入半开状态(HALF-OPEN状态),此代码为10秒。
// 半开状态:恢复接口请求调用,恢复后如果第一次请求就异常,则再次熔断
degradeRule.setTimeWindow(10);
degradeRules.add(degradeRule);
DegradeRuleManager.loadRules(degradeRules);
}
}
Sentinel控制台(源生方式)
sentinel控制台下载地址:https://github.com/alibaba/Sentinel/releases
根据自己的spring cloud版本选择对应的sentinel dashboard版本,我这边采用1.8.0
运行sentinel控制台
# 在jar路径下执行 默认是8080端口
java -jar sentinel-dashboard-1.8.0.jar
# 修改端口,用户名密码
java ‐Dserver.port=8858 ‐Dsentinel.dashboard.auth.username=kiwi ‐Dsentinel.dashboard.auth.password=kiwi ‐jar D:\develop\sentinel\sentinel‐dashboard‐1.8.0.jar
# 使用批处理文件的方式
# 1. 桌面创建sentinel_dashborad.bat
# 2. 文件中添加下面的命令
java -Dserver.port=8080 -Dsentinel.dashboard.auth.username=sentinel -Dsentinel.dashboard.auth.password=sentinel -jar D:\develop\sentinel\sentinel-dashboard-1.8.0.jar
pause
登录sentinel控制台
访问地址:http://192.168.2.6:8080
默认账号:sentinel
默认密码:sentinel
集成sentinel控制台
控制台启动后,客户端需要按照以下步骤接入到控制台。
-
添加依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>1.8.0</version> </dependency>
-
设置JVM参数,并启动项目
# 在IDEA中设置VM options参数 -Dcsp.sentinel.dashboard.server=127.0.0.1:8080
- 访问项目中随意一个接口,服务就注册到sentinel控制台中
Spring Cloud Alibaba整合sentinel
-
导入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
-
添加配置
server:
port: 8010
# 应用名称 (nacos会将该名称当做服务名称)
spring:
application:
name: order-service
cloud:
nacos:
server-addr: 192.168.2.6:8848
discovery:
username: nacos
password: nacos
namespace: public
sentinel:
transport:
dashboard: 127.0.0.1:8080
- 查看sentinel控制台
控制台规则
BlockException异常统一处理
实现BlockExceptionHandler即可
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
Logger log = LoggerFactory.getLogger(this.getClass());
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception {
log.info("BlockExceptionHandler BlockException================"+e.getRule());
Result r = null;
if (e instanceof FlowException) {
r = Result.error(100,"接口限流了");
} else if (e instanceof DegradeException) {
r = Result.error(101,"服务降级了");
} else if (e instanceof ParamFlowException) {
r = Result.error(102,"热点参数限流了");
} else if (e instanceof SystemBlockException) {
r = Result.error(103,"触发系统保护规则了");
} else if (e instanceof AuthorityException) {
r = Result.error(104,"授权规则不通过");
}
//返回json数据
response.setStatus(500);
response.setCharacterEncoding("UTF-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getWriter(), r);
}
}
这样就可以根据不同的Exception的类型进行统一的返回了,需要定制化的返回还是需要使用@SentinelResource注解。
资源的对应关系,需要知晓一下
设置add流控规则触发流控后会执行自己定义的blockHandler,设置自动识别的就会执行BlockExce统一的异常处理。同一个资源都配置了的话会执行统一异常处理。
流控规则(flow contorl)
原理是通过监控应用流量的QPS或者并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬间流量高峰冲垮,从而保障应用的高可用性。通常在服务提供端进行配置。
适用场景:
- 应对洪峰流量:秒杀,大促,下单,订单回流处理
- 消息型场景:削峰填谷,冷热启动
- 付费系统:根据使用流量付费
- API GateWay:精准控制API流量
- 任何应用:探测应用中运行的慢程序块进行限制
限流阈值类型
- QPS(Query per Sercond)
每秒请求数,服务器在一秒的时间内处理了多少个请求 - 线程数
并发线程数控制用于保护业务线程池不被慢调用耗尽。Sentinel并发控制不负责创建和管理线程池,而是简单的统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。
QPS和线程数流控的区别:
QPS是单位时间内请求的次数不能超过阈值,否则就触发流控规则;线程数是超过阈值的线程数就触发流控规则。
流控模式
-
直接
资源调用达到设置的阈值后会被直接被流控抛出异常 -
关联
关联资源触发流控后,资源名中的资源就会触发流控
例如下图:add添加资源触发限流,get获取资源也会被限流
- 链路
资源之间通过调用关系,形成调用关系树。
如下图:/order/add资源和/order/get资源都调用了hello资源就会形成一个以hello为根节点,/order/add资源和/order/get资源为叶子节点的调用关系树。
设置hello资源,入口资源为/order/get时。当hello触发限流,会对入口资源/order/get进行限流,但是同样调用hello资源的/order/add资源不会被限流,可以调用hello资源。
使用链路模式时需要注意:
-
需要添加web-context-unify配置为false
sentinel: transport: dashboard: 127.0.0.1:8080 web-context-unify: false
-
链路模式拦截不到BlockException,需要使用@SentinelResource注解,配合blockHandler处理BlockEXception。
流控效果
- 快速失败(默认)
当限流阈值达到设定的值,后面的请求就会直接失败。 - Warm Up(激增流量)
预热模式,在预热时长内,面对突然增加的大流量,直接把系统拉高至高水位可能把系统压垮,Warm Up模式就是让通过的流量缓慢的增加,在一定的时间内增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
**冷加载因子:**codeFactor默认是3,也就从阈值的1/3开始处理请求,经预热时长逐渐升高到设定的阈值。 - 排队等待(脉冲流量)
让请求匀速的排队通过,而不是直接的拒绝请求。这种效果适合于处理间歇性的突发流量,在某一秒有大量的请求到来,而下几秒则处于空闲状态,我们就可以使用这空闲的时间处理这些短时间内来的大量的请求,而不是一开始就直接拒绝所有的请求。
熔断降级
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障系统高可用的重要措施之一。我们需要对不稳定的弱依赖服务进行熔断降级,暂时切断不稳定应用,避免局部的不稳定因素导致系统整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
熔断策略
- 慢调用比例
设置允许的慢调用RT(最大的响应时间,也就是请求响应时间大于RT则统计为慢调用),在单位统计时长(控制台内默认是1000ms,高版本的控制台可以设置统计时长的值)内请求数目大于设置的最小请求数目,并且慢调用比例大于设置的比例阈值, 则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态 (HALF_OPEN 半开状态),半开状态:若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用RT则会再次被熔断。
下图的策略:统计时长1s内最少请求5次,响应时间有0.5个超过1000ms就会触发熔断,熔断时间长度为10s。熔断后进入(HALF_OPEN)半开状态。
-
异常比例
理解了慢调用比例就明白异常比例,将慢调用比例中的慢调用理解为异常数即可。页面上其他的参数含义不变。
-
异常数
异常数和异常比例意思是一样的,只是数量和比例的区别。
热点规则
热点也就是经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的数据,并对其访问进行限制,我们就可以采用热点规则。
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
注意:
- 热点规则需要使用@SentinelResource(“resourceName”)注解,否则不生效
- 参数必须是7种基本数据类型才会生效
新增热点规则:
编辑热点规则:添加热点数据信息
系统规则
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口QPS和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
- LOAD
load自适应,仅针对linux/unix-like机器生效。系统的load1作为启发指标,进行自适应系统保护。当系统load超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时就会触发系统保护(BBR阶段)。系统容量由系统的MaxQPS * MinRt估算得出,设定参考值一般是CPU cores * 2.5。
参考理解什么是LOAD:https://www.cnblogs.com/gentlemanhai/p/8484839.html - RT
平均RT。当单台机器上所有入口流量的平均RT达到阈值就会触发系统保护,单位是毫秒。 - 线程数
并发线程数。当单台机器上所有入口流量的并发线程数达到阈值就会触发系统保护。 - 入口QPS
当单台机器上所有入口流量的QPS达到阈值就会触发系统保护。 - CPU使用率
当系统CPU使用率超过阈值就会触发系统保护规则。
OpenFeign整合Sentinel
-
引入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</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-sentinel</artifactId> </dependency>
-
开启OpenFeign整合sentinel
feign: sentinel: # OpenFeign整合sentinel,默认关闭 enabled: true
-
编写OpenFeign接口
@FeignClient(value = "stock-service",path = "/stock",fallbackFactory = StockFeignServiceFallbackFactory.class) public interface StockFeignService { @RequestMapping("/deduct") String deduct(); }
-
编写fallbackFactory异常处理类
建议使用fallbackFactory,而不建议使用fallback。使用fallbackFactory可以有更灵活的操作。@Component public class StockFeignServiceFallbackFactory implements FallbackFactory<StockFeignService>{ Logger log = LoggerFactory.getLogger(this.getClass()); public StockFeignService create(Throwable throwable) { log.error("异常原因{}",throwable.getMessage(),throwable); return new StockFeignService() { public String deduct() { return "OpenFeign调用异常了"; } }; } }
当调用stock-service服务的/stock/deduct接口出现异常时,就会执行StockFeignServiceFallbackFactory中deduct 方法中的代码,在这里面就可以进行异常处理,流控处理,降级处理等操作了。GateWay整合sentinel。