Sentinel ソース コード 10 ヒューズ ダウングレード DegradeSlot

ようこそgithub.com/hsfxuebaoに注目して ください , 参考になれば幸いです. 可能だと思われる場合は、スターをクリックしてください.

1。概要

DegradeSlotは、サービス低下のサーキット ブレーカーです。

  • entry実行の過程で、溶断状態であればヒューズopen期間が経過して半開の設定が成功したかどうかを判定し、それ以外の場合はレポートを通過しません。DegradeException

  • 劣化状態half-openの場合はそのまま投げ出しますDegradeException

 

2.ヒューズ

Sentinel サーキット ブレーカーExceptionCircuitBreakerに  ResponseTimeCircuitBreakerは2 種類ありextends  AbstractCircuitBreaker  implements CircuitBreakerます。

前のスロットの実行中に、非 BlockException または何らかの未知のスローが発生した場合、エラーが設定されたエラーの数またはエラーの割合に達したかどうかを exit で判断します。

呼び出しプロセス全体が構成されたタイムアウト期間を超えると、サーキット ブレーカーもトリガーされます。

ヒューズの目的は、ヒューズの状態をハーフオープンまたはフルオープンに設定して、tryPass 検証中にパスまたは例外を返すことができるようにすることです。

構成パネルは次のとおりです。

構成項目に従って、サーキット ブレーカー インターフェイスを確認できます。

public interface CircuitBreaker {

    /**
     *  降级熔断规则
     */
    DegradeRule getRule();

    /**
     * true  判断需要降级
     */
    boolean tryPass(Context context);

    /**
     * 当前熔断器的状态
     */
    State currentState();

    /**
     * 回调方法   当请求pass通过后触发
     */
    void onRequestComplete(Context context);

    /**
     * Circuit breaker state.
     */
    enum State {
      
        OPEN,
        
        HALF_OPEN,
       
        CLOSED
    }
}
复制代码

融着規則を理解した後、融着プロセスを以下で詳しく説明します。

3. サーキットブレーカー機構の処理の流れ

画像.pngサーキット ブレーカーのトリガー条件に達すると (トリガー条件は、インターフェイスが 1 秒あたりの処理の 20% を超えたときに例外が発生し、特定のサーキット ブレーカー ルールがユーザーによって構成されている場合)、サーキット ブレーカーが有効になります。サーキット ブレーカー状態では、X 秒以内のインターフェイスへのすべてのアクセスがブロックされます。フェイル ファスト (サービスの低下)

X 秒後、次にインターフェイスが要求されると、インターフェイスはハーフ オープン状態になります。

  • リクエストインターフェースが成功した場合、通常の状態に戻ります
  • リクエスト インターフェイスが失敗した場合は、ヒューズ状態に戻り、ブロックを X 秒間続行します。

4. ヒューズの状態

ヒューズ全体の状態遷移図を見てみましょう。ここで、オープンからハーフオープンへの状態は、ヒューズ検査プロセス中にのみ発生します。画像.png

5. ソースコード分析

Sentinel のサーキット ブレーカーは、責任の連鎖の最後の 1 つ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()方法,CircuitBreakerExceptionCircuitBreakerResponseTimeCircuitBreaker两种类型的断路器,CircuitBreaker继承关系图如下:

画像.png

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()方法调用了ExceptionCircuitBreakerResponseTimeCircuitBreakeronRequestComplete()方法。

5.1 ExceptionCircuitBreaker

下面分析下比较简单的ExceptionCircuitBreaker,其对应的熔断策略为异常比例和异常数:

画像.png

详细代码如下:

public class ExceptionCircuitBreaker extends AbstractCircuitBreaker {
   
    // 策略,异常比例还是异常数
    private final int strategy;
    // 最小请求数
    private final int minRequestAmount;
    // 比例阈值
    private final double threshold;

    private final LeapArray<SimpleErrorCounter> stat;

    @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在业务方法执行后被调用,主要做了如下处理:

  • 断路器处于半开状态

    • 本次请求没有出现异常,关掉断路器
    • 本次请求出现了异常,打开断路器
  • Sentinel Dashboard降级规则中会配置最小请求数,如果请求总数小于最小请求数时不做熔断处理

  • 如果错误率或者错误数大于阈值就开启断路器

5.2 ResponseTimeCircuitBreaker

下面分析ResponseTimeCircuitBreaker

画像.png

public class ResponseTimeCircuitBreaker extends AbstractCircuitBreaker {

    private static final double SLOW_REQUEST_RATIO_MAX_VALUE = 1.0d;

    // 最大RT
    private final long maxAllowedRt;
    // 最大 慢请求比例
    private final double maxSlowRequestRatio;
    // 最小请求数量
    private final int minRequestAmount;

    private final LeapArray<SlowRequestCounter> slidingCounter;

    public ResponseTimeCircuitBreaker(DegradeRule rule) {
        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;
    }

    @Override
    public void resetStat() {
        // Reset current bucket (bucket count = 1).
        slidingCounter.currentWindow().value().reset();
    }

    @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);
        }
    }

    static class SlowRequestCounter {
        private LongAdder slowCount;
        private LongAdder totalCount;

        public SlowRequestCounter() {
            this.slowCount = new LongAdder();
            this.totalCount = new LongAdder();
        }

        public LongAdder getSlowCount() {
            return slowCount;
        }

        public LongAdder getTotalCount() {
            return totalCount;
        }

        public SlowRequestCounter reset() {
            slowCount.reset();
            totalCount.reset();
            return this;
        }

        @Override
        public String toString() {
            return "SlowRequestCounter{" +
                "slowCount=" + slowCount +
                ", totalCount=" + totalCount +
                '}';
        }
    }

    static class SlowRequestLeapArray extends LeapArray<SlowRequestCounter> {

        public SlowRequestLeapArray(int sampleCount, int intervalInMs) {
            super(sampleCount, intervalInMs);
        }

        @Override
        public SlowRequestCounter newEmptyBucket(long timeMillis) {
            return new SlowRequestCounter();
        }

        @Override
        protected WindowWrap<SlowRequestCounter> resetWindowTo(WindowWrap<SlowRequestCounter> w, long startTime) {
            w.resetTo(startTime);
            w.value().reset();
            return w;
        }
    }
}
复制代码

代码比较简单,就不做解释了。

6. 规则设置的参数

这些参数的设置,我们再直接贴官网的吧。

Field 说明 默认值
resource 资源名,即规则的作用对象
grade 熔断策略,支持慢调用比例/异常比例/异常数策略 慢调用比例
count 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
timeWindow 熔断时长,单位为 s
minRequestAmount サーキット ブレーカーによってトリガーされるリクエストの最小数。リクエストの数がこの値より少ない場合、例外率がしきい値を超えてもサーキット ブレーカーは解除されません (1.7.0 で導入)。 5
statIntervalMs 60*1000 分などの統計期間 (単位はミリ秒) (1.8.0 で導入) 1000ミリ秒
slowRatioThreshold スロー コール スケールのしきい値、スロー コール スケール モードのみが有効です (1.8.0 で導入)

参考記事

Sentinel1.8.5 ソースコード github アドレス (注)
Sentinel ソースコード解析
Sentinel 公式サイト
Sentinel DegradeSlot fuse ソースコード解析

おすすめ

転載: juejin.im/post/7150475442263162893