Sentinel(三)--熔断降级
熔断是一种系统保护措施,就是当系统的某些阈值触发到设定的临界点时,所触发的行为方式,熔断的概念在很多地方都有听到。
-
股市熔断,比如美股在7%、13%、20%的时候会分别触发熔断15分钟,在这个期间,一切交易将会终止。故事熔断的意义是稳定市场走势,给投资者冷静思考的时间。
-
电路熔断,当电流超出导线所能承受的最大电流时,则会触发熔断,此时所有电路全部断电,避免异常电流引发火灾。这种熔断场景是为了保护财产和生命安全。
在软件架构中,这类的问题也同样存在。
如下图所示,在微服务架构中,一个请求过来,可能会经过多个服务进行处理,导致整个处理链路会比较长。而在整条调用链路中,可能会因为某个节点因为网络故障导致响应时间比较长,而这个节点的阻塞将会影响这条链路的结果返回(如图)
当访问量比较高的请求下,一个后端依赖节点的延迟响应可能导致所有服务器上的所有资源在数秒内饱和。一旦出现这个问题,会导致系统资源被快速消耗,从而导致服务宕机等问题,最坏的情况会导致服务雪崩。
所以在软件系统中,为了防止这种问题的产生,也引入了熔断的概念。所以,熔断的意义是:
如果某个目标服务调用比较慢或者大量的超时,这个时候如果触发熔断机制,则可以保证后续的请求不会继续发送到目标服务上,而是直接返回降级的逻辑并且快速释放资源。如果目标服务的情况恢复了,那么熔断机制又会动态进行关闭。
Sentinel熔断的基本介绍
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断。
如何判定资源不稳定呢?
-
慢调用比例(SLOW_REQUEST_RATIO)。
-
异常比例(ERROR_RATIO)。
-
异常数(ERROR_COUNT)。
这些纬度,在Sentinel中提供了DegradeRule对象来实现规则设置,核心属性如下。
-
resource,资源名称
-
count, 阈值,[异常比例/异常数模式下为对应的阈值,慢调用比例模式下为慢调用临界 RT( 即最大的响应时间)]
-
grade,熔断模式,根据RT降级、根据异常比例、根据异常数量
-
timeWindow,熔断时间,单位为秒
下面分别对熔断的三个纬度做一个详细说明。
慢调用比例(SLOW_REQUEST_RATIO)
在一定请求次数中,一段时间内,如果有一定比例的请求响应时间大于某一个阈值,则认为目标服务异常,则在接下来的指定时间内,请求都会被自动熔断。当经过熔断时长后,熔断器会进入到探测恢复状态,若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
假设有一个场景,如果1s内连续发送10个请求,在1分钟以内,其中20%的请求平均响应时间都超过3s,则触发熔断,熔断时间为5s,针对这种情况的设置方式如下。
-
grade=CircuitBreakerStrategy.SLOW_REQUEST_RATIO, (熔断模式)
-
count=3000,最大的响应时间,单位为(毫秒)
-
TimeWindow=5 (单位为s)
-
minRequestAmount=5,最小请求数量,请求数量小于这个值,即时异常比例超出阈值也不会熔断,默认是5次。
-
slowRatioThreshold=0.2,慢调用比例阈值,仅仅在慢调用比例模式下有效。
-
statIntervalMs=1000*60, 统计时长为60秒,默认为1秒
DegradeRule rule = new DegradeRule(RESOURCE_KEY)
.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
// Max allowed response time
.setCount(3000)
// Retry timeout (in second)
.setTimeWindow(5)
// Circuit breaker opens when slow request ratio > 20%
.setSlowRatioThreshold(0.2)
.setMinRequestAmount(10)
.setStatIntervalMs(60000);
异常比例 (ERROR_RATIO)
当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值时,则触发熔断,配置方式如下:
-
grade=CircuitBreakerStrategy.ERROR_RATIO
-
count(异常比例),范围[0.0 , 1.0],代表0%~100%
-
TimeWindow=5 (单位为s)
-
minRequestAmount,最小请求数量,请求数量小于这个值,即时异常比例超出阈值也不会熔断,默认是5次
异常数量(ERROR_COUNT)
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
-
grade=CircuitBreakerStrategy.ERROR_COUNT
-
count(异常数量)
-
timeWindow=5 (熔断时间窗口,单位为s)
-
statIntervalMs=60*1000(统计时长,单位为ms)
Spring Cloud Alibaba集成Sentinel实现熔断
和sentinel限流类似。
public class DegradeExample {
private static final String RESOURCE = "hello";
public static void main(String[] args) {
initDegradeRule();
for (int i = 0; i < 2000; i++) {
try (Entry entry = SphU.entry(RESOURCE)) {
TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(10, 100));
System.out.println("current" + i + "request success");
} catch (BlockException | InterruptedException e) {
System.out.println("current" + i + ",Occur BlockException:" + e.getMessage() + "");
}
}
}
private static void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule(RESOURCE)
.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
.setCount(20)
.setTimeWindow(5)
.setSlowRatioThreshold(0.2)
.setMinRequestAmount(10)
.setStatIntervalMs(1000);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
EventObserverRegistry.getInstance().addStateChangeObserver("logging", (prevState, newState, rule1, snapshotValue) -> {
if (newState == CircuitBreaker.State.OPEN) {
System.out.println(prevState.name() + "打开了熔断状态,currentTime:" + TimeUtil.currentTimeMillis() + ",snapshotValue:" + snapshotValue);
} else {
System.err.println("prevState:" + prevState.name() + " , newState: " + newState.name() + " , curretTime:" + TimeUtil.currentTimeMillis());
}
});
}
}
Sentinel集成Nacos实现动态流控规则
在上面案例分析中,Sentinel的限流或者熔断规则,是配置在项目本地通过SPI扩展点来实现的。
既然Sentinel在Spring Cloud Alibaba生态下,我们是不是能够把Sentinel中的限流规则存储在配置中心呢?答案显然是可以的
Sentinel 的理念是开发者只需要关注资源的定义,当资源定义成功后可以动态增加各种流控降级规则。Sentinel 提供两种方式修改规则:
-
通过 API 直接修改 ( loadRules )
-
通过 DataSource 适配不同数据源修改
手动通过 API 修改比较直观,可以通过以下几个 API 修改不同的规则:
FlowRuleManager.loadRules(List<FlowRule> rules); // 修改流控规则
DegradeRuleManager.loadRules(List<DegradeRule> rules); // 修改降级规则
手动修改规则(硬编码方式)一般仅用于测试和演示,生产上一般通过动态规则源的方式来动态管理规则
动态规则管理的架构图如下。
从图中可以看到,我们可以通过Sentinel Dashboard或者Config CenterDashboard把流控规则推送到统一的配置中心(Nacos、Zookeeper等),客户端通过实现
ReadableDataSource接口来监听配置中心,从而实时获取变更的规则实现动态控制。
DataSource
DataSource 扩展常见的实现方式有:
拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是RDBMS( 关系数据库管理系统 )、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更;
推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。
Sentinel 目前支持以下数据源扩展:
-
Pull-based: 动态文件数据源、Consul, Eureka
-
Push-based: ZooKeeper, Redis, Nacos, Apollo, etcd
拉模式拓展 实现拉模式的数据源最简单的方式是继承 AutoRefreshDataSource 抽象类, 然后实现 readSource() 方法,在该方法里从指定数据源读取字符串格式的配置数据。比如基于文件的数据源。
推模式拓展 实现推模式的数据源最简单的方式是继承 AbstractDataSource 抽象类,在其构造方法中添加监听器,并实现 readSource() 从指定数据源读取字符串格式的配置数据。 比如基于 Nacos 的数据源。
控制台通常需要做一些改造来直接推送应用维度的规则到配置中心。功能示例可以参考 AHAS Sentinel 控制台的规则推送功能。改造指南可以参考在生产环境中使用 Sentinel 控制台。
基于配置形式实现动态限流
上面的动态限流规则配置,是基于SPI扩展的InitFunc来实现的
在Spring Cloud Alibaba中,我们可以直接在application.properties文件中增加如下配置,达到同样的效
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=127.0.0.1:8848
spring.cloud.sentinel.datasource.ds1.nacos.data-id=com.example.userserviceapi.IHelloService
spring.cloud.sentinel.datasource.ds1.nacos.group-id=SENTINEL_GROUP
spring.cloud.sentinel.datasource.ds1.nacos.data-type=json
spring.cloud.sentinel.datasource.ds1.nacos.rule-type=flow
spring.cloud.sentinel.datasource.ds1.nacos.username=nacos
spring.cloud.sentinel.datasource.ds1.nacos.password=nacos
nacos配置:
添加配置项之后,可以直接把InitFunc的扩展点删除,再次对接口进行压测,得到结果是相同的。
注意,Sentinel-Dashboard中配置的流控规则,默认情况下是没有做持久化的,如果需要实现配置的存储,需要改造Sentinel-Dashboard。