Código fuente de Sentinel 10 fusibles downgrade DegradeSlot

Bienvenidos a todos a prestar atención a  github.com/hsfxuebao  , espero que les sea útil. Si creen que es posible, hagan clic en Estrella.

1. Información general

DegradeSlotes un disyuntor para la degradación del servicio:

  • En entryel proceso de ejecución, si está en openestado fundido, se juzga si el período del fusible ha pasado y la configuración de medio abierto es exitosa, entonces pasará. De lo contrario, el informe no pasará.DegradeException

  • Cuando esté en un estado degradado half-open, será arrojado directamente DegradeException.

 

2. Fusible

Hay dos tipos ExceptionCircuitBreakerde   disyuntores Sentinel .ResponseTimeCircuitBreakerextends  AbstractCircuitBreaker  implements CircuitBreaker

Durante la ejecución del slot anterior, si ocurre una no-BlockException o algún lanzamiento desconocido, se juzgará en la salida si el error alcanza el número de errores configurado o la proporción de errores.

Si todo el proceso de llamada supera el tiempo de espera configurado, también se activará el disyuntor.

El propósito del fusible es establecer el estado del fusible en medio abierto o completamente abierto, de modo que se pueda devolver un pase o una excepción durante la verificación tryPass.

El panel de configuración es el siguiente:

De acuerdo con los elementos de configuración, puede echar un vistazo a la interfaz del interruptor automático:

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
    }
}
复制代码

Después de comprender las reglas de fusión, el proceso de fusión se explicará en detalle a continuación.

3. El flujo de procesamiento del mecanismo del interruptor automático.

imagen.pngCuando se alcanza la condición de activación del disyuntor (suponiendo que la condición de activación es que se produce una excepción cuando la interfaz supera el 20 % del procesamiento por segundo, y el usuario configura la regla específica del disyuntor), se habilitará el disyuntor. En el estado del disyuntor, se bloqueará todo acceso a la interfaz dentro de X segundos.falla rápida (degradación del servicio)

Después de X segundos, la próxima vez que se solicite la interfaz, estará en un estado semiabierto:

  • Si la interfaz de solicitud tiene éxito, vuelve al estado normal
  • Si la interfaz de solicitud falla, vuelva al estado de fusible y continúe Bloqueado durante X segundos

4. Estado del fusible

Echemos un vistazo al diagrama de transición de estado de todo el fusible, donde el estado de abierto a medio abierto solo ocurre durante el proceso de inspección del fusible:imagen.png

5. Análisis del código fuente

El disyuntor de Sentinel lo DegradeSlotlogra el último en la cadena de responsabilidad

@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继承关系图如下:

imagen.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,其对应的熔断策略为异常比例和异常数:

imagen.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

imagen.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 El número mínimo de solicitudes activadas por el disyuntor. Cuando el número de solicitudes es inferior a este valor, el disyuntor no se interrumpirá incluso si la tasa de excepción supera el umbral (introducido en 1.7.0) 5
statIntervalMs Duración de las estadísticas (la unidad es ms), como 60*1000 para minutos (introducido en 1.8.0) 1000ms
lentoRatioUmbral Umbral de escala de llamadas lentas, solo es válido el modo de escala de llamadas lentas (introducido en 1.8.0)

Artículo de referencia

Sentinel1.8.5 código fuente dirección github (nota)
Análisis de código fuente de
Sentinel Sitio web oficial
de Sentinel Análisis de código fuente de fusible Sentinel DegradeSlot

Supongo que te gusta

Origin juejin.im/post/7150475442263162893
Recomendado
Clasificación