本文主要来解析Sentinel熔断核心源码,基于当前最新的release版本1.8.0
达到熔断触发条件(假设触发条件为当接口每秒钟超过20%的处理产生异常,具体熔断规则由用户配置),便会开启熔断,在熔断状态下,X秒内所有该接口访问都会被Blocked快速失败(服务降级)
X秒后,下一次请求接口,此时为半开状态
Sentinel的熔断是由责任链中的最后一个DegradeSlot来实现的
@SpiOrder(-1000)
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
//在触发后续slot前执行熔断的检查
performChecking(context, resourceWrapper);
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
void performChecking(Context context, ResourceWrapper r) throws BlockException {
//通过资源名称获取所有的熔断CircuitBreaker
List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
if (circuitBreakers == null || circuitBreakers.isEmpty()) {
return;
}
for (CircuitBreaker cb : circuitBreakers) {
//cb.tryPass里面只做了状态检查,熔断是否关闭或者打开
if (!cb.tryPass(context)) {
throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
}
}
}
@Override
public void exit(Context context, ResourceWrapper r, int count, Object... args) {
Entry curEntry = context.getCurEntry();
//如果当前其他solt已经有了BlockException直接调用fireExit,不用继续走熔断逻辑了
if (curEntry.getBlockError() != null) {
fireExit(context, r, count, args);
return;
}
//通过资源名称获取所有的熔断CircuitBreaker
List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
if (circuitBreakers == null || circuitBreakers.isEmpty()) {
fireExit(context, r, count, args);
return;
}
if (curEntry.getBlockError() == null) {
//调用CircuitBreaker的onRequestComplete()方法
for (CircuitBreaker circuitBreaker : circuitBreakers) {
circuitBreaker.onRequestComplete(context);
}
}
fireExit(context, r, count, args);
}
}
进入DegradeSlot时,只会检查断路器是否已经打开,再根据是否超过了重试时间来开启半开状态,然后就直接返回是否通过。而真正判断是否需要开启断路器的地方时在exit()
方法里面,因为这个方法是在业务方法执行后调用的,断路器需要收集业务异常或者业务方法的执行时间来判断是否打开断路器
先来看进入DegradeSlot的entry()
方法,这里调用了CircuitBreaker的tryPass()
方法,CircuitBreaker有ExceptionCircuitBreaker和ResponseTimeCircuitBreaker两种类型的断路器,CircuitBreaker继承关系图如下:
entry()
方法实际上调用了AbstractCircuitBreaker的tryPass()
方法,这里只做了一个处理,如果断路器开启,但是上一个请求距离现在已经过了重试间隔时间就开启半启动状态
public abstract class AbstractCircuitBreaker implements CircuitBreaker {
@Override
public boolean tryPass(Context context) {
if (currentState.get() == State.CLOSED) {
return true;
}
if (currentState.get() == State.OPEN) {
//如果断路器开启,但是上一个请求距离现在已经过了重试间隔时间就开启半启动状态
return retryTimeoutArrived() && fromOpenToHalfOpen(context);
}
return false;
}
exit()
方法调用了ExceptionCircuitBreaker和ResponseTimeCircuitBreaker的onRequestComplete()
方法
下面分析下比较简单的ExceptionCircuitBreaker,其对应的熔断策略为异常比例和异常数:
详细代码如下:
public class ExceptionCircuitBreaker extends AbstractCircuitBreaker {
@Override
public void onRequestComplete(Context context) {
Entry entry = context.getCurEntry();
if (entry == null) {
return;
}
Throwable error = entry.getError();
//异常时间窗口计数器
SimpleErrorCounter counter = stat.currentWindow().value();
//异常数加1
if (error != null) {
counter.getErrorCount().add(1);
}
//总数加1
counter.getTotalCount().add(1);
handleStateChangeWhenThresholdExceeded(error);
}
private void handleStateChangeWhenThresholdExceeded(Throwable error) {
//断路器已开直接返回
if (currentState.get() == State.OPEN) {
return;
}
//断路器处于半开状态
if (currentState.get() == State.HALF_OPEN) {
if (error == null) {
//本次请求没有出现异常,关掉断路器
fromHalfOpenToClose();
} else {
//本次请求出现了异常,打开断路器
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时不做熔断处理 minRequestAmount时配置在熔断规则里面的
if (totalCount < minRequestAmount) {
return;
}
double curCount = errCount;
if (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) {
//如果熔断策略配置的是窗口时间内错误率就需要做百分比的计算
curCount = errCount * 1.0d / totalCount;
}
//错误率或者错误数大于阈值就开启断路器
if (curCount > threshold) {
transformToOpen(curCount);
}
}
ExceptionCircuitBreaker在业务方法执行后被调用,主要做了如下处理:
1)断路器处于半开状态
2)Sentinel Dashboard降级规则中会配置最小请求数,如果请求总数小于最小请求数时不做熔断处理
3)如果错误率或者错误数大于阈值就开启断路器
参考:
源码分析 Sentinel DegradeSlot 熔断