Sentinel之Slots插槽源码分析降级规则(六)

一、引子

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

二、降级策略

我们通常用以下几种方式来衡量资源是否处于稳定的状态:

  • 平均响应时间 (DEGRADE_GRADE_RT):当资源的平均响应时间超过阈值(DegradeRule 中的 count,以 ms 为单位)之后,资源进入准降级状态。接下来如果持续进入 5 个请求,它们的 RT 都持续超过这个阈值,那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回(抛出 DegradeException)。
  • 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。

注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex) 记录业务异常。示例:

Entry entry = null;
try {
  entry = SphU.entry(key, EntryType.IN, key);

  // Write your biz code here.
  // <<BIZ CODE>>
} catch (Throwable t) {
  if (!BlockException.isBlockException(t)) {
    //这里会统计异常数
    Tracer.trace(t);
  }
} finally {
  if (entry != null) {
    entry.exit();
  }
}

开源整合模块,如 Sentinel Dubbo Adapter, Sentinel Web Servlet Filter 或 @SentinelResource 注解会自动统计业务异常,无需手动调用。

三、源码分析

3.1 DegradeSlot

public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args)
        throws Throwable {
       //规则检查
        DegradeRuleManager.checkDegrade(resourceWrapper, context, node, count);
        fireEntry(context, resourceWrapper, node, count, args);
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        fireExit(context, resourceWrapper, count, args);
    }
}

进入DegradeRuleManager中,可以发现与前面的限流规则一样,这个是用于管理降级的类。
我们重点看下checkDegrade方法。

3.2 DegradeRuleManager

    public static void checkDegrade(ResourceWrapper resource, Context context, DefaultNode node, int count)
        throws BlockException {
        if (degradeRules == null) {
            return;
        }

        List<DegradeRule> rules = degradeRules.get(resource.getName());
        if (rules == null) {
            return;
        }

        for (DegradeRule rule : rules) {
            if (!rule.passCheck(context, node, count)) {
                throw new DegradeException(rule.getLimitApp());
            }
        }
    }
  1. degradeRule是对应资源的额降级规则,是一个map。
  2. 获取到对应资源的降级规则。
  3. 调用Degrade的passCheck检测是否需要降级。
  4. 若降级了则抛出DegradeException异常。

3.3 DegradeRule

降级规则的参数

  • count: RT临界值或者异常数、异常比列
  • timeWindow:降级的时间间隔,单位秒
  • grade:阈值类型RT、异常数、异常比例

下面看下passCheck方法:

 @Override
    public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) {
        if (cut) {
            return false;
        }

        ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(this.getResource());
        if (clusterNode == null) {
            return true;
        }

        if (grade == RuleConstant.DEGRADE_GRADE_RT) {
            double rt = clusterNode.avgRt();
            if (rt < this.count) {
                passCount.set(0);
                return true;
            }

            // Sentinel will degrade the service only if count exceeds.
            if (passCount.incrementAndGet() < RT_MAX_EXCEED_N) {
                return true;
            }
        } else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
            double exception = clusterNode.exceptionQps();
            double success = clusterNode.successQps();
            long total = clusterNode.totalQps();
            // if total qps less than RT_MAX_EXCEED_N, pass.
            if (total < RT_MAX_EXCEED_N) {
                return true;
            }

            double realSuccess = success - exception;
            if (realSuccess <= 0 && exception < RT_MAX_EXCEED_N) {
                return true;
            }

            if (exception / success < count) {
                return true;
            }
        } else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
            double exception = clusterNode.totalException();
            if (exception < count) {
                return true;
            }
        }

        synchronized (lock) {
            if (!cut) {
                // Automatically degrade.
                cut = true;
                ResetTask resetTask = new ResetTask(this);
                pool.schedule(resetTask, timeWindow, TimeUnit.SECONDS);
            }

            return false;
        }
    }

关键参数:

  1. cut:资源是否已经降级标志,为true表示已经降级了。
  2. passCount:若达到降级条件后,连续复合降级条件的次数,默认为RT_MAX_EXCEED_N(5)次。

过程大致如下:

  1. 如果已经降级了(cut为ture),则阻塞;否则获取clusterNode
  2. 降级规则为RuleConstant.DEGRADE_GRADE_RT,先获取资源的平均RT;若RT小于设置的阈值count,则请求通过并设置passCount为0,否在判断passCount是否小于5,若小于则请求通过;否则请求阻塞。
  3. 降级规则为RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO异常比例时,先获取资源的exception数,success数,total数。若total数小于5请求通过;exception是小于5请求通过;异常比列exception/total小于设置的阈值则请求通过;否则请求阻塞。
  4. 降级规则为RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT异常数时,若异常数小于设置的阈值时则请求通过;否则请求阻塞。
  5. 若上述有规则不满足,则说明该资源需要降级;降级时需要先设置cut为true,并启动一个定时任务来设置降级时间窗口后降级的重置。该任务如下:
    private static final class ResetTask implements Runnable {

        private DegradeRule rule;

        ResetTask(DegradeRule rule) {
            this.rule = rule;
        }

        @Override
        public void run() {
            //设置passCount为0
            rule.getPassCount().set(0);
            //设置cut为false
            rule.setCut(false);
        }
    }

四、我的总结

  1. 介绍了Sentinel的j降级规则以及降级原理。
  2. 降级有三种策略,rt,异常数,异常比例;目前dashboard控制台能够设置应该就只有rt和异常比例了。
  3. 通过设置passCount来避免出现偶尔一个请求异常的情况,提高降级的准确性。
  4. 生产环境下建议使用设置rt策略来控制降级。

以上内容,若有不当之处,请指正。

猜你喜欢

转载自blog.csdn.net/weixin_33938733/article/details/87050930