上一篇文章《微服务组件Feign&Nacos配置中心使用》
开源地址 Spring Cloud Alibaba Sentinel
官网地址 https://sentinelguard.io/zh-cn/
概念
服务雪崩效应:因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程
导致服务不可用的原因:
激增流量
不稳定服务依赖
解决方案
稳定性、恢复性
常见的容错机制
在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源消耗。加入超时机制,就释放资源。由于释放资源速度较快,一定程度上可以抑制资源耗尽的问题
规定服务一定的访问量所能承受的临界值,如下图,提前每秒查询量规定为500,当有800访问量时,300会进行限流,可以对着300访问进行降级(设置一个请求,返回请求过多,请稍后再试),从而有效地保护服务器
原理:用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好地提醒信息),而不是无休止的等待或者看到系统崩溃
远程服务不稳定或网络抖动时暂时关闭,就叫服务熔断
就好比现实世界的断路器,断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路被烧毁
软件世界的断路器可以这样理解:实时监控应用,如果发现在一定时间内失败次数/失败率达到一定阈值,就"跳闸",断路器打开—此时,请求直接返回,而不去调用原本调用的接口。跳闸一段时间后(例如1分钟),断路器会进行半开状态,这是一个瞬间态,此时允许一次请求调用该调的接口,如果成功,则断路器关闭,应用正常调用;如果调用依旧不成功,断路器继续回到打开状态,过段时间再进入半开状态尝试—通过"跳闸",应用可以保护自己,而且避免浪费资源;而通过半开的设计,可实现应用的"自我修复"
同理,当依赖的服务有大量超时时,再让新的请求去访问根本没有意义,只会无谓的消耗现有的资源。比如我们设置了超时时间为1s,如果短时间内有大量请求实在1s内都得不到响应,就意味着这个服务出现了异常,此时就没有必要再让其他的请求去访问这个依赖了,这个时候就应该使用断路器避免资源浪费
出现的情况有如下
出现了慢SQL,慢SQL导致应用越来越慢,最后整个应用卡挂了
应用依赖了第三方的服务,第三方服务突然不响应了,造成应用线程也被挂在第三方应用上无法返回,最后自己的线程也耗尽,也无法处理新的请求
程序内部某个方法调用持续异常。这个时候调用这个方法已经毫无意义了,而且会影响主业务流程
服务降级
所谓服务降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的fallback(回退)回调,返回一个缺省值。例如:(备用接口/缓存/mock数据)。这样做,虽然服务水平下降,但是可用,比直接服务器挂掉要强。通常在较弱的依赖配置降级服务
Sentinel是什么
Sentinel 是阿里巴巴开源的,面向分布式服务架构的高可用防护组件
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性
源码地址:https://github.com/alibaba/Sentinel
中文官方文档:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
英文官方文档:https://github.com/alibaba/Sentinel/wiki
Sentinel 具有以下特征:
阿里云提供了企业级的Sentinel服务,应用高可用服务 AHAS
Sentinel 和 Hystrix 对比
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动、匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架适配 | Servlet、Spring Cloud、Dubbo、gRPC等 | Servlet、Spring Cloud Netflix |
如何使用英文文档:
https://github.com/alibaba/Sentinel/wiki/How-to-Use
如何使用中文文档:https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。同时 Sentinel 也提供相关 API,供您来定制自己的规则策略
Sentinel 支持以下几种规则:流量控制规则、熔断降级规则、系统保护规则、来源访问控制规则 和 热点参数规则
简介
Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果
使用 Sentinel 来进行资源保护,主要分为几个步骤:
定义资源
定义规则
检验规则是否生效
(一般设置在服务提供方
)
测试
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-coreartifactId>
<version>1.8.0version>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-annotation-aspectjartifactId>
<version>1.8.0version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.24version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 8005
package com.vinjcent.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
@RestController
@Slf4j //用作当前类的日志输出的
public class UserController {
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(value = "/hello")
public String hello(){
Entry entry = null;
try {
//sentinel 针对资源名称进行限制的请求的接口
entry = SphU.entry(RESOURCE_NAME);
//被保护的业务逻辑
String str = "hello world";
log.info("====" + str + "====");
return str;
} catch (BlockException e) {
//资源访问阻止,被限流或被降级
//进行相应的处理操作
log.info("block");
return "被流控了!";
} catch (Exception e){
//若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(e,entry);
}finally {
if(entry != null){
entry.exit();
}
}
return null;
}
/*
spring 的初始化方法
*/
@PostConstruct //在spring容器创建bean的时候,就会对其进行初始化
private static void initFlowRules(){
//流控规则集合,因为可能不止监控一个接口
List<FlowRule> rules = new ArrayList<FlowRule>();
//流控
FlowRule rule = new FlowRule();
//设置受保护的资源,为哪个资源进行流量控制
rule.setResource(RESOURCE_NAME);
//设置流控规则 QPS
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//设置受保护的资源阈值
//1s之内不能超过一次访问,不然就会进行流控
rule.setCount(1);
rules.add(rule);
//加载配置好的规则
FlowRuleManager.loadRules(rules);
}
}
每隔一秒访问一次http://localhost:8005/hello
快速访问http://localhost:8005/hello
流量控制规则 (FlowRule)
流量规则的定义
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 模式(1)或并发线程数模式(0) | QPS 模式 |
limitApp | 流控针对的调用来源 | default ,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 | 直接拒绝 |
clusterMode | 是否集群限流 | 否 |
注解@SentinelResource
降低接口的侵入性,降低耦合
使用注解改善接口中资源定义和被流控降级后的处理方法
【注意】注解方式埋点不支持 private 方法
@SentinelResource
用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource
注解包含以下属性:
value
:资源名称,必需项(不能为空)
entryType
:entry 类型,可选项(默认为 EntryType.OUT
)
blockHandler
/ blockHandlerClass
: blockHandler
对应处理 BlockException
的函数名称,可选项。blockHandler 函数访问范围需要是 public
,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException
。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass
为对应的类的 Class
对象,注意对应的函数必需为 static 函数,否则无法解析
fallback/fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore
里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
返回值类型必须与原函数返回值类型一致
方法参数列表需要和原函数一致,或者可以额外多一个 Throwable
类型的参数用于接收对应的异常
fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass
为对应的类的 Class
对象,注意对应的函数必需为 static 函数,否则无法解析
defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore
里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
Throwable
类型的参数用于接收对应的异常fallbackClass
为对应的类的 Class
对象,注意对应的函数必需为 static 函数,否则无法解析exceptionsToIgnore
(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出
1.8.0 版本开始,defaultFallback
支持在类级别进行配置。
【注】1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException
)进行处理,不能针对业务异常进行处理。
特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException
时只会进入 blockHandler
处理逻辑。若未配置 blockHandler
、fallback
和 defaultFallback
,则被限流降级时会将 BlockException
直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException
)
package com.vinjcent.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.vinjcent.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
@RestController
@Slf4j //用作当前类的日志输出的
public class UserController {
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(value = "/hello")
public String hello(){
Entry entry = null;
try {
//sentinel 针对资源名称进行限制的请求的接口
entry = SphU.entry(RESOURCE_NAME);
//被保护的业务逻辑
String str = "hello world";
log.info("====" + str + "====");
return str;
} catch (BlockException e) {
//资源访问阻止,被限流或被降级
//进行相应的处理操作
log.info("block");
return "被流控了!";
} catch (Exception e){
//若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(e,entry);
}finally {
if(entry != null){
entry.exit();
}
}
return null;
}
/*
spring 的初始化方法
*/
@PostConstruct //在spring容器创建bean的时候,就会对其进行初始化
private static void initFlowRules(){
//流控规则集合,因为可能不止监控一个接口
List<FlowRule> rules = new ArrayList<FlowRule>();
//添加规则一
//流控
FlowRule rule1 = new FlowRule();
//设置受保护的资源,为哪个资源进行流量控制
rule1.setResource(RESOURCE_NAME);
//设置流控规则 QPS
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
//设置受保护的资源阈值
//1s之内不能超过一次访问,不然就会进行流控
rule1.setCount(1);
rules.add(rule1);
//添加规则二
FlowRule rule2 = new FlowRule();
rule2.setResource(USER_RESOURCE_NAME);
rule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule2.setCount(1);
rules.add(rule2);
//加载配置好的规则
FlowRuleManager.loadRules(rules);
}
/**
* @SentinelResource 改善接口中资源定义和被流控降级后的处理方法
* 使用需要
* 1. 添加依赖 sentinel-annotation-aspectj
* 2. 在config中配置注解支持的bean SentinelResourceAspect
* value 定义资源
* blockHandler 设置流控降级后的处理方法(默认该方法必须生命在同一个类中)
* 如果不想在同一个类里面,可以把它放在pojo对象当中,同时设置该方法为静态方法
* fallback 当接口出现了异常,就可以交给fallback指定的方法进行处理
* 如何 blockHandler 和 fallback 都同时设置了,则 blockHandler 优先级更高
* exceptionsToIgnore 排除哪些异常可以不同处理,如ArithmeticException.class
* @param id
* @return
*/
@RequestMapping("/user")
@SentinelResource(value = USER_RESOURCE_NAME,
blockHandler = "blockHandlerForFlow",
fallback = "fallbackHandlerForFlow",
exceptionsToIgnore = {ArithmeticException.class})
public User getUser(String id){
int a = 1/0;
return new User("vinjcent");
}
/**
* 注意点:
* 1. 一定要是public
* 2. 返回值和原方法必须保证一致
* 3. 参数也要包含原方法的参数,并且顺序要一致
* 4. 可以在参数最后添加 BlockException 异常,可以区分是什么规则的处理方法
* @param id
* @param e
* @return
*/
public User blockHandlerForFlow(String id, BlockException e){
e.printStackTrace();
return new User("流控!");
}
public User fallbackHandlerForFlow(String id, Throwable e){
e.printStackTrace();
return new User("异常!");
}
}
package com.vinjcent.config;
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SentinelConfig {
@Bean
public SentinelResourceAspect sentinelResourceAspect(){
return new SentinelResourceAspect();
}
}
访问http://localhost:8005/user
快速访问http://localhost:8005/user
同时,我们可以设置exceptionsToIgnore
属性排除一些不必要的异常
(一般设置在服务消费方
)
概述
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置
针对Sentinel 1.8.0 及以上版本
熔断策略
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 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断熔断降级规则 (DegradeRule)
熔断降级规则包含下面几个重要的属性
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即规则的作用对象 | |
grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
count | 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 | > 不是 >= |
timeWindow | 熔断时长,单位为 s | |
minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) | 5 |
statIntervalMs | 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) | 1000 ms |
slowRatioThreshold | 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) |
/*
降级规则
*/
@PostConstruct
private void initDegradeRules(){
/* 降级规则 异常 */
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource(DEGRADE_RESOURCE_NAME);
//设置规则的策略: 异常数
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
//触发熔断阈值(异常数): 2
rule.setCount(2);
//出发熔断最小的请求数
rule.setMinRequestAmount(2);
//多长时间段内,触发的阈值熔断 单位: ms
rule.setStatIntervalMs(60*1000);
//触发条件: 一分钟内,执行了两次请求,并出现了2次异常,就会触发熔断
//(熔断降级独有的)熔断的持续时长 单位: s
//一旦出发了熔断,再次请求对应的接口就会直接调用 降级方法
//10s过了之后,就会进入半开状态,并恢复接口请求调用
//如果恢复之后再一次请求就出现了异常,且再次熔断,就不会根据设置的条件进行判断
rule.setTimeWindow(10);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
@RequestMapping(value = "/degrade")
@SentinelResource(
value = DEGRADE_RESOURCE_NAME,
entryType = EntryType.IN,
blockHandler = "blockHandlerForDegrade"
)
public User degrade(String id){
//异常数\比例
throw new RuntimeException("异常");
// return new User("正常!");
}
public User blockHandlerForDegrade(String id,BlockException e){
return new User("熔断降级!");
}
http://localhost:8005/degrade
【注意】在使用@SentinelResource
注解时,参数中的blockHandler
要遵循以下规则
blockHandler 函数访问范围需要是 public
参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException
返回值类型必须与原函数返回值类型一致
方法参数列表需要和原函数一致,或者可以额外多一个 Throwable
类型的参数用于接收对应的异常
fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass
为对应的类的 Class
对象,注意对应的函数必需为 static 函数,否则无法解析
源码文档链接
下载控制台jar包链接
下载好后,找到下载位置,通过命令窗口运行
java -jar sentinel-dashboard-1.8.0.jar
默认密码用户名均为:sentinel
java -Dserver.port=8858 -Dsentinel.dashboard.auth.username=sentinel -Dsentinel.dashboard.auth.password=123456 -jar sentinel-dashboard-1.8.0.jar
sentinel_dashbord.bat
文件,内容如下java -Dserver.port=8858 -Dsentinel.dashboard.auth.username=sentinel -Dsentinel.dashboard.auth.password=123456 -jar "D:\Program Files\Sentinel\sentinel-dashboard-1.8.0.jar"
pause
每次启动双击该文件即可
引入JAR包
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-transport-simple-httpartifactId>
<version>1.8.0version>
dependency>
配置启动参数
启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=consoleIp:port
指定控制台地址和端口。若启动多个应用,则需要通过 -Dcsp.sentinel.api.port=xxxx
指定客户端监控 API 的端口(默认是 8719)
重新启动该服务
访问任意一接口
查看监控控制台
控制台的一些特性可以通过配置项来进行配置,配置项主要有两个来源:System.getProperty()
和 System.getenv()
,同时存在时后者可以覆盖前者
通过环境变量进行配置时,因为不支持
.
所以需要将其更换为_
配置项 | 类型 | 默认值 | 最小值 | 描述 |
---|---|---|---|---|
auth.enabled | boolean | true | - | 是否开启登录鉴权,仅用于日常测试,生产上不建议关闭 |
sentinel.dashboard.auth.username | String | sentinel | - | 登录控制台的用户名,默认为 sentinel |
sentinel.dashboard.auth.password | String | sentinel | - | 登录控制台的密码,默认为 sentinel |
sentinel.dashboard.app.hideAppNoMachineMillis | Integer | 0 | 60000 | 是否隐藏无健康节点的应用,距离最近一次主机心跳时间的毫秒数,默认关闭 |
sentinel.dashboard.removeAppNoMachineMillis | Integer | 0 | 120000 | 是否自动删除无健康节点的应用,距离最近一次其下节点的心跳时间毫秒数,默认关闭 |
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭 |
sentinel.dashboard.autoRemoveMachineMillis | Integer | 0 | 300000 | 距离最近心跳时间超过指定时间是否自动删除失联节点,默认关闭 |
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭 |
server.servlet.session.cookie.name | String | sentinel_dashboard_cookie | - | 控制台应用的 cookie 名称,可单独设置避免同一域名下 cookie 名冲突 |
创建一个新的模块 springcloud-provider-dept-sentinel-8005
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
dependencies>
server:
port: 8005
spring:
application:
name: springcloud-provider-dept-sentinel # 应用名称
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8858 # 监控地址
@RestController
@RequestMapping("/dept")
@SuppressWarnings("all")
public class DeptController {
@Value("${server.port}")
private int port;
@GetMapping("/msg")
public String getMessage(){
return "获取服务成功!"+port;
}
}
Sentinel控制台规则配置详解
最合适场景
如何配置规则
@RestController
@RequestMapping("/dept")
@SuppressWarnings("all")
public class DeptController {
@Value("${server.port}")
private int port;
@GetMapping("/msg")
@SentinelResource(value = "flow",blockHandler = "flowBlockHandler")
public String getMessage(){
return "获取服务成功!"+port;
}
public String flowBlockHandler(BlockException e){
return "流控异常!";
}
}
再次频繁访问http://localhost:8005/dept/msg不会出现流控异常
重新设置
或持久化
并发线程数
并发数控制用于保护业务线程不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为了应对太多线程占用的情况,业务有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求就会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置
测试
@GetMapping("/msgThread")
@SentinelResource(value = "msgThread",blockHandler = "flowBlockHandler")
public String getMessageThread() throws InterruptedException {
//睡眠5秒,模拟多线程
TimeUnit.SECONDS.sleep(5);
return "获取服务成功!"+port;
}
如果不想再每个接口中加上@SentinelResource
注解
可以自定义BlockExceptionHandler的实现类统一处理BlockException
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
//getRule() 获取规则的详细信息
log.info("BlockExceptionHandler ==========================="+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数据
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getWriter(),r);
}
}
public class Result<T> {
private Integer code;
private String msg;
private T data;
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static Result error(Integer code, String msg){
return new Result(code, msg);
}
}
将Controller中的@SentinelResource
注解去掉
访问http://localhost:8005/dept/msg,使得在sentinel监控控制台显示出来
流控模式
基于调用关系的流量控制。调用关系包括调用方、被调用方;一个方法可能会调用其它方法,形成一个调用链路的层次关系
资源调用达到设置的阈值之后直接被流控抛出异常
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如数据库中同一个字段的读操作和写操作存在争抢,读的速度过高会影响写的速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可以使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db
和 write_db
这两个资源分别代表数据库读写,我们可以给 read_db
设置限流规则来达到写的优先目的:设置 strategy
为 RuleConstant.STRATEGY_RELATE
同时设置 refResource
为 write_db
。这样当写库操作过于频繁时,都数据库的请求会被限流
测试
@RequestMapping("/add")
public String add(){
return "生成订单";
}
@RequestMapping("/query")
public String query(){
return "查询订单";
}
JMeter下载地址
// DepartmentService接口
public interface DepartmentService {
String getDepartment();
}
// DepartmentServiceImpl类
@Service
public class DepartmentServiceImpl implements DepartmentService {
@Override
@SentinelResource(value = "getDept")
public String getDepartment() {
return "获取部门";
}
}
发现该接口没有被监控,原因:sentinel默认将我们的调用链路收敛了
需要在application.yml
中添加配置
server:
port: 8005
spring:
application:
name: springcloud-provider-dept-sentinel # 应用名称
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8858 # 监控地址
# 默认将我们的调用链路收敛了,设置为false取消收敛
web-context-unify: false
连续访问:http://localhost:8005/dept/test1
http://localhost:8005/dept/test2
发现连续访问 /dept/test2
会报500错误,因为使用了@SentinelResource
注解,没有在调用的方法注解@SentinelResource
中加上blockHandler=“”,自定义的MyBlockExceptionHandler.class失效了,需要手动添加一个blockHandler方法
@Service
public class DepartmentServiceImpl implements DepartmentService {
@Override
@SentinelResource(value = "getDept", blockHandler = "blockHandlerGetDept")
public String getDepartment() {
return "获取部门";
}
public String blockHandlerGetDept(BlockException e){
return "getDepartment流控异常";
}
}
快速访问:http://localhost:8005/dept/test2
快速访问:http://localhost:8005/dept/test1
快速失效
(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水准时
Warm Up(激增流量)
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓缓增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮
冷加载因子:codeFactor默认是3,即请求 QPS 从threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示
配置如图所示
匀速排队(脉冲流量)
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法
该方法的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。假设有这样的情景:在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是第一秒直接拒绝多余的请求
【注意】匀速排队模式暂时不支持 QPS > 1000 的场景
测试
场景一:直接失败
/dept/msg
接口配置流控可以看到下图,10个线程请求,由于阈值是5,所以每执行一次10个请求,有5个请求是失败的,并且每执行完一次10个请求就会等待5s(由JMeter设置)
场景二:排队等待
/dept/msg
的流控设置小结
保护自身的手段(通常在consumer端组合配置)
触发熔断后的处理逻辑示例
慢调用比例
慢调用比例 (SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断
接口代码
@GetMapping("/msgThread")
// @SentinelResource(value = "msgThread",blockHandler = "flowBlockHandler")
public String getMessageThread() throws InterruptedException {
//睡眠5秒,模拟多线程
TimeUnit.SECONDS.sleep(3);
return "获取服务成功!"+port;
}
/dept/msgThread
配置降级规则异常比例
异常比例 (ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%
@RequestMapping("/exception")
public String error(){
int i = 1/0;
return "hello";
}
/dept/exception
配置异常比例降级/dept/exception
快速访问10次,查看结果树异常数
测试
新建一个模块springcloud-comsuner-dept-openfeign-sentinel-80
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
dependencies>
DeptClientService
接口和DeptClientServiceFallback.class
类/**
* name 指定调用rest接口所对应的服务名
* path 指定调用rest接口所在的Controller指定的@RequestMapping
*/
@FeignClient(name = "springcloud-provider-dept",path = "/dept",fallback = DeptClientServiceFallback.class)
public interface DeptClientService {
@GetMapping("/msg")
String getMessage();
}
@Component
public class DeptClientServiceFallback implements DeptClientService{
@Override
public String getMessage() {
return "降级了!";
}
}
@RestController
@RequestMapping("/consumer")
@SuppressWarnings("all")
public class DeptController {
@Autowired
private DeptClientService clientService;
@RequestMapping("/dept/msg")
public String getMessage(){
return this.clientService.getMessage();
}
}
访问以上controller中的接口
热点参数限流
何为热点?热点即是经常访问的数据
常用场景:
实现原理:热点淘汰策略(LRU)+Token Bucket 流控
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看作是一种特殊的流量控制,仅对包含热点参数的资源调用生效
单机阈值
情景一
假设参数大部分都是热点参数,需要针对热点参数进行流控,后续额外针对普通的参数值进行流控
情景二
假设大部分值都是普通参数,额外的参数都是热点参数,后续需要对额外的热点参数进行流控
测试
在springcloud-comsuner-dept-openfeign-sentinel-80中的controller新增一个接口/dept/get/{id}
修改对应application.yml
文件
server:
port: 80
spring:
application:
name: springcloud-consumer-dept-feign-sentinel
# 配置nacos
cloud:
nacos:
# nacos 服务地址
server-addr: 192.168.159.100:7070,192.168.159.100:7080,192.168.159.100:7090
discovery:
username: nacos # nacos用户名
password: nacos
namespace: public # 分隔开发环境和测试环境使用
# 不向服务注册中心注册自己
register-enabled: false
# 设置监控服务
sentinel:
transport:
dashboard: 127.0.0.1:8858
# 默认将我们的调用链路收敛了,需要打开
web-context-unify: false
feign:
sentinel:
# openfeign整合sentinel
enabled: true
4.访问http://localhost/consumer/dept/get/1
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性
maxQps * minRt
估算得出。设定参考值一般是CPU cores * 2.5
测试
其它配置都如同,只要超出自己在该模块中所配置的系统保护条件,就会触发系统保护规则
Sentinel规则的推送有下面三种模式
推送模式 | 说明 | 优点 | 缺点 |
---|---|---|---|
原始模式 | API将规则推送至客户端并直接更新到内存中,扩展写数据源(WrtiableDataSource) | 简单,无任何依赖 | 不保证一致性;规则存在内存中,重启即消失。那个不建议用于生产环境 |
Pull 模式 | 扩展写数据源(WrtiableDataSource),客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心是 RDBMS、文件等 | 无任何依赖,规则持久化 | 不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题 |
Push 模式 | 扩展读数据源(ReadableDataSouce),规则中心同意推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper等配置中心,这种方式有更好的实时性和一致性保证。生产环境下一般采用push模式的数据源 | 规则持久化;一致性;快速 | 引入第三方依赖 |
原始模式
如果不做任何修改,Dashboard 的推送规则方式是通过 API 将规则推送至客户端并直接更新到内存中
这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能用于生产环境
pull 模式
pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的 WritableDataSourceRegistery
中
push 模式
生产环境一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心(Zookeeper、Nacos、Apollo等等),推送的操作不应由 Sentinel 客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台 —> 配置中心 —> Sentinel 数据源 —> Sentinel。而不是经 Sentinel 数据源推送至配置中心
概述
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性
FlowSlot
会根据预设的规则,结合前面 NodeSelectorSlot
、ClusterBuilderSlot
、StatisticSlot
统计出来的实时信息进行流量控制
限流的直接表现是在执行 Entry nodeA = SphU.entry(resourceName)
的时候抛出 FlowException
异常。FlowException
是 BlockException
的子类,您可以捕捉 BlockException
来自定义被限流之后的处理逻辑
同一个资源可以创建多条限流规则。FlowSlot
会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕。
一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
resource
:资源名,即限流规则的作用对象(接口)count
: 限流阈值grade
: 限流阈值类型(QPS 或并发线程数,QPS 模式(1)或并发线程数模式(0))limitApp
: 流控针对的调用来源,若为 default
则不区分调用来源strategy
: 调用关系限流策略(直接、链路、关联)controlBehavior
: 流量控制效果(直接拒绝、Warm Up、匀速排队)测试
application.yml
server:
port: 8005
spring:
application:
name: springcloud-provider-dept-sentinel # 应用名称
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8858 # 监控地址
# 默认将我们的调用链路收敛了,需要打开
web-context-unify: false
datasource:
flow-rule: # 数据源可以自定义配置
nacos:
server-addr: 192.168.159.100:7070
# 由于在Linux开启了权限,需要设置用户名、密码
username: nacos
password: nacos
# nacos配置文件的dataId
dataId: springcloud-provider-dept-sentinel-flow-rule
# 规则类型选择
rule-type: flow
FLOW("flow", FlowRule.class),
DEGRADE("degrade", DegradeRule.class),
PARAM_FLOW("param-flow", ParamFlowRule.class),
SYSTEM("system", SystemRule.class),
AUTHORITY("authority", AuthorityRule.class),
GW_FLOW("gw-flow", "com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule"),
GW_API_GROUP("gw-api-group", "com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition");
/dept/msg
接口,使得该接口在 Sentinel 控制台输出缺点:
在nacos配置文件中,虽然已经对访问资源进行了持久化,但是要想在 Sentinel 控制台对该资源进行修改,并且同步到Nacos注册中心中的配置文件是不可实现的,需要进行拓展
下一篇文章《微服务分布式组件—Seata》