当前位置: 首页 > news >正文

Sentinel架构篇 - 熔断降级

熔断降级

概念

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用其它模块,可能是一个远程服务、数据库、或者第三方 API 等。然而,被依赖的服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,导致请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会堆积,最终可能会耗尽业务自身的线程池,甚至服务本身变得不可用。

现在的微服务架构都是分布式的,由非常多的服务组成。不同的服务之间相互调用,形成复杂的调用链路。链路中某一环不稳定,可能会层层级联,最终导致整个链路不可用。因此需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定的调用,避免局部不稳定因素导致整体的雪崩。熔断降级通常在客户端(调用端)进行配置。

熔断可以类比成生活中的保险丝,一旦电流过载,保险丝就会断开。

Sentinel 熔断降级基于熔断器模式 (circuit breaker pattern) 实现。熔断器内部维护了一个熔断器的状态机,状态机的转换关系如下图所示:

在这里插入图片描述

熔断器有三种状态:

  • Closed 状态:也是初始状态,该状态下,熔断器会保持闭合,对资源的访问直接通过熔断器的检查。
  • Open 状态:断开状态,熔断器处于开启状态,对资源的访问会被切断。
  • Half-Open 状态:半开状态,该状态下除了探测流量,其余对资源的访问也会被切断。探测流量指熔断器处于半开状态时,会周期性的允许一定数目的探测请求通过,如果探测请求能够正常的返回,代表探测成功,此时熔断器会重置状态到 Closed 状态,结束熔断;如果探测失败,则回滚到 Open 状态。

熔断策略

Sentinel 提供了如下三种熔断策略。

  • 慢调用比例(SLOW_REQUEST_RATIO):选择慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),如果请求的时间大于该阈值则被统计为慢调用。当单位统计时长(statIntervalMs)内请求的数量大于设置的最小请求数量,并且慢调用的比例大于阈值,则接下来的熔断时长(timeWindow)内请求自动被熔断。经过熔断时长后,熔断器进入探测恢复状态(Half-Open 状态),如果接下来的一个请求的响应时间小于设置的慢调用 RT 则结束熔断;如果大于则再次被熔断。慢调用比例的阈值范围为 [0.0, 1.0],代表 0% - 100%。
  • 异常比例(ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数量大于设置的最小请求数量,并且异常比例大于阈值,则接下来的熔断时长(timeWindow)内请求自动被熔断。经过熔断时长后,熔断器进入探测恢复状态(Half-Open 状态),如果接下来的一个请求成功完成(没有错误)则结束熔断;否则再次被熔断。异常比例的阈值范围为 [0.0, 1.0] ,代表 0% - 100%。
  • 异常数(ERROR_COUNT):当单位统计时长(statIntervalMs)内的异常数量超过阈值之后,则接下来的熔断时长(timeWindow)内请求自动被熔断。经过熔断时长后,熔断器进入探测恢复状态(Half-Open 状态),如果接下来的一个请求成功完成(没有错误)则结束熔断;否则再次被熔断。

熔断降级规则

字段说明默认值
resource资源名,即规则作用的对象
grade熔断策略,支持慢调用比例/异常比例/异常数慢调用比例
count慢调用比例模式下对应慢调用RT(超过该值即为慢调用);异常比例/异常数模式下为对应的阈值
timeWindow熔断时长,单位为秒
minRequestAmount熔断触发的最小请求数,请求数小于该值时即使异常比例超过阈值也不会熔断(1.7.0 版本引入)5
statIntervalMs统计时长,单位为毫秒(1.8.0 版本引入)1000
slowRationThreshold慢调用比例阈值,仅慢调用比例模式有效(1.8.0 版本引入)

同一个资源可以同时有多个熔断降级规则。

实际操作

在 Nacos 的控制台中的配置管理/配置列表中,在 public 的命名空间中创建一个如下配置:

dataId:spring-cloud-demo-consumer-sentinel-degrade

group:DEFAULT

[{"resource": "/hello/say","limitApp": "default","grade": 0,"count": 200,"timeWindow": 10,"statIntervalMs": 1000,"slowRatioThreshold": 0.6   }
]

如果 1 秒内,请求数量至少达到 200,并且(请求的响应时间超过 200 毫秒即为慢调用)慢调用的比例达到 60%,则进行熔断,熔断时长为 10 秒。


对应的客户端的配置文件如下:

spring:application:name: spring-cloud-demo-consumercloud:nacos:discovery:server-addr: 10.211.55.11:8848,10.211.55.12:8848,10.211.55.13:8848enabled: truesentinel:transport:dashboard: 127.0.0.1:9000eager: truedatasource:degrade-nacos-datasource:nacos:server-addr: 10.211.55.11:8848,10.211.55.12:8848,10.211.55.13:8848group-id: DEFAULT_GROUPnamespace: publicdata-id: ${spring.application.name}-sentinel-degradedata-type: jsonrule-type: degradeusername: nacospassword: nacos

DegradeSlot

负责熔断降级规则的判断。

@Spi(order = Constants.ORDER_DEGRADE_SLOT)
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {@Overridepublic void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,boolean prioritized, Object... args) throws Throwable {// 校验熔断降级规则performChecking(context, resourceWrapper);fireEntry(context, resourceWrapper, node, count, prioritized, args);}void performChecking(Context context, ResourceWrapper r) throws BlockException {// 由DegradeRuleManager负责加载所有的断路器List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());// 如果断路器列表为空,则直接返回if (circuitBreakers == null || circuitBreakers.isEmpty()) {return;}// 遍历断路器列表,只要有一个断路器判定请求不通过,则抛出DegradeException异常for (CircuitBreaker cb : circuitBreakers) {if (!cb.tryPass(context)) {throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());}}}@Overridepublic void exit(Context context, ResourceWrapper r, int count, Object... args) {Entry curEntry = context.getCurEntry();// 如果调用过程中存在BlockException异常,则直接返回if (curEntry.getBlockError() != null) {fireExit(context, r, count, args);return;}// 由DegradeRuleManager负责加载所有的断路器List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());// 如果断路器列表为空,则直接返回if (circuitBreakers == null || circuitBreakers.isEmpty()) {fireExit(context, r, count, args);return;}// 如果调用过程中不存在BlockException异常if (curEntry.getBlockError() == null) {// 遍历断路器列表,触发每个断路器的onRequestComplete方法的回调for (CircuitBreaker circuitBreaker : circuitBreakers) {circuitBreaker.onRequestComplete(context);}}fireExit(context, r, count, args);}
}

接下来看下断路器如何判断请求是否通过的。

AbstractCircuitBreaker

AbstractCircuitBreaker(DegradeRule rule, EventObserverRegistry observerRegistry) {AssertUtil.notNull(observerRegistry, "observerRegistry cannot be null");if (!DegradeRuleManager.isValidRule(rule)) {throw new IllegalArgumentException("Invalid DegradeRule: " + rule);}this.observerRegistry = observerRegistry;this.rule = rule;this.recoveryTimeoutMs = rule.getTimeWindow() * 1000;
}

接下来看下 tryPass 方法的处理逻辑。

@Override
public boolean tryPass(Context context) {// 如果断路器的状态是CLOSED,则返回true,表示请求通过if (currentState.get() == State.CLOSED) {return true;}// 如果断路器的状态是OPENif (currentState.get() == State.OPEN) {// 判断是否到了熔断结束时间,如果到了则尝试将断路器的状态从OPEN变为HALF-OPENreturn retryTimeoutArrived() && fromOpenToHalfOpen(context);}// 剩余情况,返回false,表示请求不通过return false;
}

retryTimeoutArrived 方法

判断是否到了熔断结束时间

protected boolean retryTimeoutArrived() {// 判断当前时间 >= 下一次的熔断结束时间return TimeUtil.currentTimeMillis() >= nextRetryTimestamp;
}

fromOpenToHalfOpen 方法

尝试将断路器的状态从OPEN变为HALF-OPEN

protected boolean fromOpenToHalfOpen(Context context) {// 尝试将断路器的状态从OPEN更新为HALF_OPENif (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) {// 触发所有CircuitBreakerStateChangeObserver的onStateChange方法回调notifyObservers(State.OPEN, State.HALF_OPEN, null);Entry entry = context.getCurEntry();entry.whenTerminate(new BiConsumer<Context, Entry>() {@Overridepublic void accept(Context context, Entry entry) {// 如果调用过程中存在BlockException异常if (entry.getBlockError() != null) {// 将断路器的状态从HALF_OPEN更新为OPENcurrentState.compareAndSet(State.HALF_OPEN, State.OPEN);// 触发所有CircuitBreakerStateChangeObserver的onStateChange方法回调notifyObservers(State.HALF_OPEN, State.OPEN, 1.0d);}}});return true;}return false;
}

接下来重点看下 AbstractCircuitBreaker 的子类对于 onRequestComplete 方法的具体实现。

ResponseTimeCircuitBreaker

关注响应时间的断路器实现

public ResponseTimeCircuitBreaker(DegradeRule rule) {// 统计时长由熔断降级规则的statIntervalMs参数指定,默认1000,即1秒this(rule, new SlowRequestLeapArray(1, rule.getStatIntervalMs()));
}ResponseTimeCircuitBreaker(DegradeRule rule, LeapArray<SlowRequestCounter> stat) {super(rule);AssertUtil.isTrue(rule.getGrade() == RuleConstant.DEGRADE_GRADE_RT, "rule metric type should be RT");AssertUtil.notNull(stat, "stat cannot be null");this.maxAllowedRt = Math.round(rule.getCount());this.maxSlowRequestRatio = rule.getSlowRatioThreshold();this.minRequestAmount = rule.getMinRequestAmount();this.slidingCounter = stat;
}

看下 ResponseTimeCircuitBreaker 对于 onRequestComplete 方法的具体实现。

@Override
public void onRequestComplete(Context context) {SlowRequestCounter counter = slidingCounter.currentWindow().value();Entry entry = context.getCurEntry();if (entry == null) {return;}long completeTime = entry.getCompleteTimestamp();if (completeTime <= 0) {completeTime = TimeUtil.currentTimeMillis();}long rt = completeTime - entry.getCreateTimestamp();// 如果响应时间超过了阈值(对应熔断降级规则中的count参数)if (rt > maxAllowedRt) {// 慢请求数指标加一counter.slowCount.add(1);}// 总请求数指标加一counter.totalCount.add(1);handleStateChangeWhenThresholdExceeded(rt);
}

接下来看下 handleStateChangeWhenThresholdExceeded 方法的处理逻辑。

private void handleStateChangeWhenThresholdExceeded(long rt) {// 如果断路器的状态是OPEN,则直接返回if (currentState.get() == State.OPEN) {return;}// 如果断路器的状态是HALF_OPENif (currentState.get() == State.HALF_OPEN) {// 如果请求的响应时间超过了阈值if (rt > maxAllowedRt) {// 将断路器的状态更新为OPEN,然后更新下一次的熔断结束时间fromHalfOpenToOpen(1.0d);} else {// 将断路器的状态更新为CLOSED,然后重置慢请求数、总请求数指标fromHalfOpenToClose();}return;}List<SlowRequestCounter> counters = slidingCounter.values();long slowCount = 0;long totalCount = 0;// 累加慢请求数、总请求数for (SlowRequestCounter counter : counters) {slowCount += counter.slowCount.sum();totalCount += counter.totalCount.sum();}// 如果总请求数 < 熔断降级规则中的minRequestAmount参数,则直接返回if (totalCount < minRequestAmount) {return;}// 计算慢调用比例double currentRatio = slowCount * 1.0d / totalCount;// 如果慢调用比例 > 熔断降级队则中的slowRatioThreshold参数值(默认1)if (currentRatio > maxSlowRequestRatio) {// 将断路器的状态更新为OPEN,然后更新下一次的熔断结束时间transformToOpen(currentRatio);}// 如果当前的慢调用比例达到了100%if (Double.compare(currentRatio, maxSlowRequestRatio) == 0 &&Double.compare(maxSlowRequestRatio, SLOW_REQUEST_RATIO_MAX_VALUE) == 0) {// 将断路器的状态更新为OPEN,然后更新下一次的熔断结束时间transformToOpen(currentRatio);}
}

ExceptionCircuitBreaker

关注异常比例、异常数的断路器实现

public ExceptionCircuitBreaker(DegradeRule rule) {// 统计时长由熔断降级规则的statIntervalMs参数指定,默认1000,即1秒this(rule, new SimpleErrorCounterLeapArray(1, rule.getStatIntervalMs()));
}ExceptionCircuitBreaker(DegradeRule rule, LeapArray<SimpleErrorCounter> stat) {super(rule);this.strategy = rule.getGrade();boolean modeOk = strategy == DEGRADE_GRADE_EXCEPTION_RATIO || strategy == DEGRADE_GRADE_EXCEPTION_COUNT;AssertUtil.isTrue(modeOk, "rule strategy should be error-ratio or error-count");AssertUtil.notNull(stat, "stat cannot be null");this.minRequestAmount = rule.getMinRequestAmount();this.threshold = rule.getCount();this.stat = stat;
}

看下 ExceptionCircuitBreaker 对于 onRequestComplete 方法的具体实现。

@Override
public void onRequestComplete(Context context) {Entry entry = context.getCurEntry();if (entry == null) {return;}Throwable error = entry.getError();SimpleErrorCounter counter = stat.currentWindow().value();if (error != null) {// 对异常请求数指标加一counter.getErrorCount().add(1);}// 对总请求数指标加一counter.getTotalCount().add(1);handleStateChangeWhenThresholdExceeded(error);
}

接下来看下 handleStateChangeWhenThresholdExceeded 方法的处理逻辑。

private void handleStateChangeWhenThresholdExceeded(Throwable error) {// 如果断路器的状态是OPEN,则直接返回if (currentState.get() == State.OPEN) {return;}// 如果断路器的状态是HALF_OPENif (currentState.get() == State.HALF_OPEN) {if (error == null) {// 将断路器的状态更新为CLOSED,然后重置慢请求数、总请求数指标fromHalfOpenToClose();} else {// 将断路器的状态更新为OPEN,然后更新下一次的熔断结束时间fromHalfOpenToOpen(1.0d);}// 直接返回return;}List<SimpleErrorCounter> counters = stat.values();long errCount = 0;long totalCount = 0;// 累加错误请求数、总请求数for (SimpleErrorCounter counter : counters) {errCount += counter.errorCount.sum();totalCount += counter.totalCount.sum();}// 如果总请求数 < 熔断降级规则中的minRequestAmount参数,则直接返回if (totalCount < minRequestAmount) {return;}double curCount = errCount;// 如果策略是统计异常数比例,则将异常数比例转化成错误请求数if (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) {curCount = errCount * 1.0d / totalCount;}// 如果错误请求数 > 阈值if (curCount > threshold) {// 将断路器的状态更新为OPEN,然后更新下一次的熔断结束时间transformToOpen(curCount);}
}
http://www.lryc.cn/news/34657.html

相关文章:

  • shell脚本的一些记录 与jenkins的介绍
  • JVM的了解与学习
  • 提升数字品牌的5个技巧
  • java通过反射获取加了某个注解的所有的类
  • Warshall算法
  • vector中迭代器失效的问题及解决办法
  • 【蓝桥杯刷题训练营】day05
  • 线程中断interrupt导致sleep产生的InterruptedException异常
  • ubuntu的快速安装与配置
  • 人工智能AI工具汇总(AIGC ChatGPT时代个体崛起)
  • 【rust-grpc-proxy】在k8s中,自动注入代理到pod中,再不必为grpc调试而烦恼
  • VisualStudio2022制作多项目模板及Vsix插件
  • 仿写简单IOC
  • liunx下安装node exporter
  • lambda函数
  • 【Python入门第二十七天】Python 日期
  • C++基础知识【5】数组和指针
  • Vim使用操作命令笔记
  • 【论文阅读】Robust Multi-Instance Learning with Stable Instances
  • 洛谷 P5116 [USACO18DEC]Mixing Milk B
  • 华为OD机试 - 最左侧冗余覆盖子串(C 语言解题)【独家】
  • 《Netty》从零开始学netty源码(三)之SelectorProvider
  • 实验7 图像水印
  • 如何实现大文件断点续传、秒传
  • 备战蓝桥python——完全平方数
  • WebRTC中的NAT穿透
  • SpringCloud-高级篇(一)
  • 电脑自动重启是什么原因?详细解说
  • 2023美国大学生数学建模竞赛E题思路
  • 蓝桥杯三月刷题 第五天