Sentinel中DegradeSlot的原理
DegradleSlot负责熔断降级,支持异常熔断和慢请求熔断,异常熔断又支持异常数量和异常比例两种策略。本文就来分析一下降级插槽和熔断器的工作原理。
DegradeSlot
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java
@Override
public 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 {
// 根据资源名称查找熔断器
List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
if (circuitBreakers == null || circuitBreakers.isEmpty()) {
return;
}
// 遍历熔断器
for (CircuitBreaker cb : circuitBreakers) {
// 判断能不能通过
if (!cb.tryPass(context)) {
throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
}
}
}
在这个插槽中,遍历了熔断器来判断是否需要降级处理。
熔断器
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/AbstractCircuitBreaker.java
@Override
public boolean tryPass(Context context) {
// Template implementation.
// 当前熔断器处于关闭状态,则放行
if (currentState.get() == State.CLOSED) {
return true;
}
if (currentState.get() == State.OPEN) { // 开启状态
// For half-open state we allow a request for probing.
/*
* 判断当前是否已经可以关闭熔断器了,如果可以则尝试从开启状态转换到关闭状态,
* 成功推动这次转换的请求是可以通过的,但是如果该请求还是发生错误,则又会改为全开状态。
*/
return retryTimeoutArrived() && fromOpenToHalfOpen(context);
}
return false; // 半开启状态,则直接拒绝请求
}
protected boolean retryTimeoutArrived() {
// 判断当前时间是否超过了熔断器允许被关闭的时间,主要用来判断是否可以关闭熔断器了
return TimeUtil.currentTimeMillis() >= nextRetryTimestamp;
}
protected boolean fromCloseToOpen(double snapshotValue) {
State prev = State.CLOSED;
if (currentState.compareAndSet(prev, State.OPEN)) {
// 更新熔断器下一次关闭的时间
updateNextRetryTimestamp();
notifyObservers(prev, State.OPEN, snapshotValue);
return true;
}
return false;
}
protected boolean fromOpenToHalfOpen(Context context) {
// 将状态修改为半开状态
if (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) {
// 通知观察者
notifyObservers(State.OPEN, State.HALF_OPEN, null);
// 获取资源入口
Entry entry = context.getCurEntry();
/*
* 向entry注册exit回调,之所以要注册回调,是修复1638号issue提出的BUG。
* 在当前请求结束后执行下面的accept回调函数
*/
entry.whenTerminate(new BiConsumer<Context, Entry>() {
@Override
public void accept(Context context, Entry entry) {
// Note: This works as a temporary workaround for https://github.com/alibaba/Sentinel/issues/1638
// Without the hook, the circuit breaker won't recover from half-open state in some circumstances
// when the request is actually blocked by upcoming rules (not only degrade rules).
// 如果改为半开后发生了错误,
if (entry.getBlockError() != null) {
// Fallback to OPEN due to detecting request is blocked
// 则再次从半开状态修改为全开状态
currentState.compareAndSet(State.HALF_OPEN, State.OPEN);
notifyObservers(State.HALF_OPEN, State.OPEN, 1.0d);
}
}
});
return true;
}
return false;
}
protected boolean fromHalfOpenToOpen(double snapshotValue) {
// 从半开状态改为全开
if (currentState.compareAndSet(State.HALF_OPEN, State.OPEN)) {
// 更新熔断器下一次关闭的时间
updateNextRetryTimestamp();
notifyObservers(State.HALF_OPEN, State.OPEN, snapshotValue);
return true;
}
return false;
}
protected boolean fromHalfOpenToClose() {
if (currentState.compareAndSet(State.HALF_OPEN, State.CLOSED)) {
// 重置数据统计
resetStat();
// 通知观察者
notifyObservers(State.HALF_OPEN, State.CLOSED, null);
return true;
}
return false;
}
protected void transformToOpen(double triggerValue) {
State cs = currentState.get();
switch (cs) {
case CLOSED:
fromCloseToOpen(triggerValue);
break;
case HALF_OPEN:
fromHalfOpenToOpen(triggerValue);
break;
default:
break;
}
}
上面的状态转移方法比较多,请参考下图:
如果熔断器是开启状态,则先判断熔断器的熔断时间是否超过上限,如果是则将熔断器的状态从全开改为半开。如果改为了半开后,则先放行该请求,如果该请求还是发生了错误,则又改为全开状态。那什么时候从关闭状态到全开状态呢?这就要看具体的实现类了。
异常熔断器
异常熔断器处理两种策略,异常数量和异常比例。
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreaker.java
@Override
public void onRequestComplete(Context context) {
Entry entry = context.getCurEntry();
if (entry == null) {
return;
}
Throwable error = entry.getError();
SimpleErrorCounter counter = stat.currentWindow().value();
// 对error进行累加
if (error != null) {
counter.getErrorCount().add(1);
}
// 对total进行累加
counter.getTotalCount().add(1);
handleStateChangeWhenThresholdExceeded(error);
}
private void handleStateChangeWhenThresholdExceeded(Throwable error) {
// 如果已经是全开状态,则不做额外的处理
if (currentState.get() == State.OPEN) {
return;
}
// 如果熔断器已经是半开状态,那么尝试进行状态转换
if (currentState.get() == State.HALF_OPEN) {
// In detecting request
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();
}
// 如果请求总数太低,则不进行熔断
if (totalCount < minRequestAmount) {
return;
}
double curCount = errCount;
// 如果降级策略是异常比例
if (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) {
// Use errorRatio
curCount = errCount * 1.0d / totalCount; // 计算错误比率
}
// 如果超过了阈值,则开启熔断器
if (curCount > threshold) {
transformToOpen(curCount);
}
}
如果熔断器是半开,则根据是否发生了错误将其转为全开或关闭状态。如果熔断器是关闭状态,则判断异常数量和异常比例是否超过了阈值,如果是则修改为全开状态。另外注意如果请求数量太少,则不进行熔断。
慢请求熔断器
java
sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreaker.java
@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();
// 如果请求耗时超过了阈值,那么累加慢请求数量
if (rt > maxAllowedRt) {
counter.slowCount.add(1);
}
// 累加总的请求数量
counter.totalCount.add(1);
handleStateChangeWhenThresholdExceeded(rt);
}
private void handleStateChangeWhenThresholdExceeded(long rt) {
// 如果已经是全开状态,则不做额外处理
if (currentState.get() == State.OPEN) {
return;
}
// 如果是半开状态,则判断是否是忙请求,如果是则转换到开启状态;否则转换到关闭状态
if (currentState.get() == State.HALF_OPEN) {
// In detecting request
// TODO: improve logic for half-open recovery
if (rt > maxAllowedRt) {
fromHalfOpenToOpen(1.0d);
} else {
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();
}
// 如果请求总数太小,则不进行熔断
if (totalCount < minRequestAmount) {
return;
}
double currentRatio = slowCount * 1.0d / totalCount;
// 如果慢请求比率超过了阈值,则转换到开启状态
if (currentRatio > maxSlowRequestRatio) {
transformToOpen(currentRatio);
}
// 如果慢请求比率等于阈值,也转换到开启状态
if (Double.compare(currentRatio, maxSlowRequestRatio) == 0 &&
Double.compare(maxSlowRequestRatio, SLOW_REQUEST_RATIO_MAX_VALUE) == 0) {
transformToOpen(currentRatio);
}
}
如果熔断器是半开,如果响应时间也超过了阈值,则改为全开;否则改为关闭。如果熔断器是关闭状态,则判断慢请求的比例是否达到了阈值,如果是的话也改为全开。另外注意,如果请求数量太少,则不进行熔断。
总结
总之,熔断器经历过熔断时长以后,会先改为半开状态,如果改为半开后的第一个请求通过,则关闭熔断器,否则重新改为全开状态。