Sentinel源码分析(第四篇):部分功能插槽原理分析

1. 前言

在之前的文章中,分析了Sentinel如何统计数据指标的。本篇文章主要是分析调用链路上的一些功能插槽的功能和实现,包括系统自适应、黑白名单控制、流量控制、熔断降级四个功能。

2. Sentinel功能插槽规则

2.1. 规则的定义

在分析各种功能插槽之前,先看一下Sentinel对规则的定义。

Sentinel使用Rule表示规则,Rule是一个接口,Rule只有一个方法,passCheck(),表示规则是否通过。

AbstractRule实现了Rule,是一个抽象类,里面定义了这个规则属于的资源,限制的app。所有具体的规则都是继承该规则。

2.2. 规则的加载或更新

当定义了具体的规则之后,需要加载规则到系统中,这个加载过程是由规则管理器负责的,比如定义了系统自适应规则SystemRule,会由对应的规则管理器SystemRuleManager加载该规则。 下面就以SystemRuleManager为例,分析规则的加载。

每一个规则管理器中都会有一个继承了SimplePropertyListener规则改变处理器作为变量,当规则改变之后,做相关的更新处理工作。每个规则管理器中会定义一个内部类作为该变量的具体实现,比如SystemRuleManager:

//创建一个文件处理器
private final static SystemPropertyListener listener = new SystemPropertyListener();

//定义一个处理该规则的处理器类
static class SystemPropertyListener extends SimplePropertyListener<List<SystemRule>>{
。。。
}

复制代码

可以看到,SystemRuleManager创建了一个SystemPropertyListener作为文件改变的处理器,SystemPropertyListener是其内部的一个内部类。

规则管理器中还会定义一个SentinelProperty,SentinelProperty代表Sentinel中的配置信息,在这个地方就是代表配置的规则。然后会将定义的规则处理器添加给规则,当规则变更的时候,使用对应的规则处理器进行处理。

private static SentinelProperty<List<SystemRule>> currentProperty = new DynamicSentinelProperty<List<SystemRule>>();

currentProperty.addListener(listener);

复制代码

以SystemRule更新为例,当定义了规则之后,调用SystemRuleManager的loadRules()会进行规则的更新。

public static void loadRules(List<SystemRule> rules) {
    currentProperty.updateValue(rules);
}
复制代码

调用loadRules()的时候其实是调用DynamicSentinelProperty的updateValues()方法:

 public boolean updateValue(T newValue) {
    if (isEqual(value, newValue)) {
        return false;
    }
    RecordLog.info("[DynamicSentinelProperty] Config will be updated to: " + newValue);

    value = newValue;
    for (PropertyListener<T> listener : listeners) {
        listener.configUpdate(newValue);
    }
    return true;
}
复制代码

可以看到,DynamicSentinelProperty的updateValues()的方法中其实是调用规则改变处理器的configUpdate()方法,所以具体是如何更新的是每个规则管理器里面定义的内部类SimplePropertyListener。

以上就是规则的加载过程,各种规则的加载大致流程是一样的,只是具体的加载逻辑都是由自己的规则管理器中定义的SimplePropertyListener来负责更新。

3. 系统自适应限流

3.1. 什么是系统自适应限流

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

系统规则支持以下的模式:

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。

  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。

  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。

  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

以上文字摘抄至sentinel官方文档。

2.2. SystemRule

SystemRule代表系统自适应保护的规则定义,定义了load、cpu、qps、rt、thread维度的参数。

public class SystemRule extends AbstractRule {
    /**
     * negative value means no threshold checking.
     */
    private double highestSystemLoad = -1;
    /**
     * cpu usage, between [0, 1]
     */
    private double highestCpuUsage = -1;
    private double qps = -1;
    private long avgRt = -1;
    private long maxThread = -1;
}
复制代码

2.3. SystemRuleManager

SystemRuleManager是实现系统自适应保护的核心类,负责管理系统自适应规则,包括加载更新规则,检查当前入口调用是否满足规则。

public class SystemRuleManager {

    private static volatile double highestSystemLoad = Double.MAX_VALUE;
    private static volatile double highestCpuUsage = Double.MAX_VALUE;
    private static volatile double qps = Double.MAX_VALUE;
    private static volatile long maxRt = Long.MAX_VALUE;
    private static volatile long maxThread = Long.MAX_VALUE;
    
    private static volatile boolean highestSystemLoadIsSet = false;
    private static volatile boolean highestCpuUsageIsSet = false;
    private static volatile boolean qpsIsSet = false;
    private static volatile boolean maxRtIsSet = false;
    private static volatile boolean maxThreadIsSet = false;

    /**
     * 是否存在指定的系统规则,即是否调用SystemRuleManager.loadRules()加载规则
     */
    private static AtomicBoolean checkSystemStatus = new AtomicBoolean(false);

    /**
     * 一个获取系统当前的load和cpu使用的任务
     */
    private static SystemStatusListener statusListener = null;

    /**
     * 系统规则改变后的处理器,主要将规则设置为指定的规则
     */
    private final static SystemPropertyListener listener = new SystemPropertyListener();
    
      /**
     * 系统规则改变后的处理器,主要将规则设置为指定的规则
     */
    private static SentinelProperty<List<SystemRule>> currentProperty = new DynamicSentinelProperty<List<SystemRule>>();

    /**
     * 
     */
    private final static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1,
        new NamedThreadFactory("sentinel-system-status-record-task", true));

    static {
        checkSystemStatus.set(false);
        statusListener = new SystemStatusListener();
        //定时任务5秒后开始,每秒执行一次
        scheduler.scheduleAtFixedRate(statusListener, 5, 1, TimeUnit.SECONDS);
        currentProperty.addListener(listener);
    }
    
    ...
}
复制代码

上面的参数主要含义为:

  • 前面定义的十个参数主要是描述系统自适应保护参数的。
  • checkSystemStatus:是否需要进行系统自适应保护。
  • statusListener:负责获取系统运行参数的一个任务。
  • listener:系统规则改变后的处理器。
  • currentProperty:当前配置文件。
  • scheduler:一个负责运行statusListener的定时任务。

再看静态代码块,主要是初始化scheduler,设置运行的任务和频率,并且将系统规则处理器添加给currentProperty。

SystemRuleManager中会负责规则的加载,这个部分在第二小节中已经讲过。

SystemRuleManager中还有一个最重要的方法,checkSystem(),负责校验进入的请求是否满足系统自适应设置的规则。

public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockException {
    // Ensure the checking switch is on.
    //如果没有加入系统规则,则不需要检查
    if (!checkSystemStatus.get()) {
        return;
    }

    // for inbound traffic only
    //如果不是系统入口的资源,不检查
    if (resourceWrapper.getType() != EntryType.IN) {
        return;
    }

    //根据全局的ClusterNode获取数据指标,ClusterNode是一个类变量,全局唯一,类加载后生成。
    // 每次通过check后再StatisticSlot中累加统计

    // total qps
    double currentQps = Constants.ENTRY_NODE == null ? 0.0 : Constants.ENTRY_NODE.successQps();
    if (currentQps > qps) {
        throw new SystemBlockException(resourceWrapper.getName(), "qps");
    }

    // total thread
    int currentThread = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.curThreadNum();
    if (currentThread > maxThread) {
        throw new SystemBlockException(resourceWrapper.getName(), "thread");
    }

    double rt = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.avgRt();
    if (rt > maxRt) {
        throw new SystemBlockException(resourceWrapper.getName(), "rt");
    }

    // load. BBR algorithm.
    //如果当前机器的load大于设置的值
    if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {
        if (!checkBbr(currentThread)) {
            throw new SystemBlockException(resourceWrapper.getName(), "load");
        }
    }

    // cpu usage
    if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {
        if (!checkBbr(currentThread)) {
            throw new SystemBlockException(resourceWrapper.getName(), "cpu");
        }
    }
}
复制代码

checkSystem()中具体的逻辑如上,具体实现原理可以参考官方文档。

2.4. SystemSlot

SystemSlot是负责系统自适应保护的功能插槽,每一个内部entry进入的时候都会进行系统自适应保护校验。SystemSlot很简单,主要是在entry()方法中调用 SystemRuleManager.checkSystem()方法进行系统自适应保护的检查,具体逻辑还是在SystemRuleManager中。

3. AuthoritySlot

黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

3.1. AuthorityRule

AuthorityRule代表对黑白名单设置的规则,其中主要包括个属性设置,一个是限制的来源名称,多个来源使用逗号分隔。还需要设置限制的时白名单还是黑名单。

private String limitApp;
private int strategy = RuleConstant.AUTHORITY_WHITE;
复制代码

3.2. AuthorityRuleManager

AuthorityRuleManager和上一节的SystemRuleManager类似,是负责管理黑白名单的加载的。先看一下其中的属性定义:

public final class AuthorityRuleManager {

    /**
     * 保存权限配置,以resource为key,所以同一个resource只能有一个最新的权限规则
     */
    private static Map<String, Set<AuthorityRule>> authorityRules = new ConcurrentHashMap<>();

    /**
     * 规则改变处理器
     */
    private static final RulePropertyListener LISTENER = new RulePropertyListener();

    /**
     * 当前规则
     */
    private static SentinelProperty<List<AuthorityRule>> currentProperty = new DynamicSentinelProperty<>();

    static {
        currentProperty.addListener(LISTENER);
    }
}
复制代码

和SystemRuleManager类似,AuthorityRuleManager也有一个规则改变的处理器RulePropertyListener,这个RulePropertyListener是AuthorityRuleManager的一个内部类,然后也有一个表示当前配置的配置类currentProperty,当需要加载更新规则的时候,逻辑和SystemRuleManager类似,所以不再描述。

3.3. AuthoritySlot

AuthoritySlot是负责处理黑白名单的功能插槽,当entry进入的时候,调用checkBlackWhiteAuthority()进行校验。

public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
    throws Throwable {
    checkBlackWhiteAuthority(resourceWrapper, context);
    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

/**
 * 检查黑白名单
 * @param resource
 * @param context
 * @throws AuthorityException
 */
void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {

    //获取所有的规则
    Map<String, Set<AuthorityRule>> authorityRules = AuthorityRuleManager.getAuthorityRules();

    if (authorityRules == null) {
        return;
    }

    //获取指定resource的规则
    Set<AuthorityRule> rules = authorityRules.get(resource.getName());
    if (rules == null) {
        return;
    }

    for (AuthorityRule rule : rules) {
        if (!AuthorityRuleChecker.passCheck(rule, context)) {
            throw new AuthorityException(context.getOrigin(), rule);
        }
    }
}
复制代码

可以看到,这个地方主要是获取所有的黑白名单规则,然后遍历调用AuthorityRuleChecker.passCheck()进行校验,校验的具体逻辑很简单,就不在此描述了。

4. 流量控制

流量控制原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

4.1. FlowRule

FlowRule代表流量控制规则,定义了流量控制的各种参数。

public class FlowRule extends AbstractRule {
    /**
     * The threshold type of flow control (0: thread count, 1: QPS).
     */
    private int grade = RuleConstant.FLOW_GRADE_QPS;

    /**
     * Flow control threshold count.
     */
    private double count;

    /**
     * Flow control strategy based on invocation chain.
     *
     * {@link RuleConstant#STRATEGY_DIRECT} for direct flow control (by origin);
     * {@link RuleConstant#STRATEGY_RELATE} for relevant flow control (with relevant resource);
     * {@link RuleConstant#STRATEGY_CHAIN} for chain flow control (by entrance resource).
     */
    private int strategy = RuleConstant.STRATEGY_DIRECT;

    /**
     * Reference resource in flow control with relevant resource or context.
     */
    private String refResource;

    /**
     * Rate limiter control behavior.
     * 0. default(reject directly), 1. warm up, 2. rate limiter, 3. warm up + rate limiter
     */
    private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;

    private int warmUpPeriodSec = 10;

    /**
     * Max queueing time in rate limiter behavior.
     */
    private int maxQueueingTimeMs = 500;

    private boolean clusterMode;
    /**
     * Flow rule config for cluster mode.
     */
    private ClusterFlowConfig clusterConfig;

    /**
     * The traffic shaping (throttling) controller.
     */
    private TrafficShapingController controller;
复制代码

一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:

  • resource:资源名,即限流规则的作用对象
  • count: 限流阈值
  • grade: 限流阈值类型(QPS 或并发线程数)
  • limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
  • strategy: 调用关系限流策略
  • controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)

4.2. FlowSlot

FlowSlot是负责流量控制的功能插槽,具体实现如下:

     * 流量规则检查器
     */
    private final FlowRuleChecker checker;

    public FlowSlot() {
        this(new FlowRuleChecker());
    }
    
  ...
  
     @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        checkFlow(resourceWrapper, context, node, count, prioritized);

        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
        throws BlockException {
        checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
    }


    /**
     * 规则提供者
     */
    private final Function<String, Collection<FlowRule>> ruleProvider = new Function<String, Collection<FlowRule>>() {
        @Override
        public Collection<FlowRule> apply(String resource) {
            // Flow rule map should not be null.
            Map<String, List<FlowRule>> flowRules = FlowRuleManager.getFlowRuleMap();
            return flowRules.get(resource);
        }
    };
    ...
复制代码

FlowSLot中定义了一个FlowRuleChecker,FlowRuleChecker负责对限流规则进行检查。可以看到,FlowSlot实际的限流逻辑调用FlowRuleChecker实现的。在调用FlowRuleChecker的checkFlow()方法的时候,需要传入一个ruleProvider,这是一个Function。

4.3. FlowRuleChecker

上面说到FlowRuleChecker是负责流量控制校验的,FlowSlot中调用FlowRuleChecker的checkFlow()方法:

public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
                      Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
    if (ruleProvider == null || resource == null) {
        return;
    }
    Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
    if (rules != null) {
        for (FlowRule rule : rules) {
            if (!canPassCheck(rule, context, node, count, prioritized)) {
                throw new FlowException(rule.getLimitApp(), rule);
            }
        }
    }
}
复制代码

checkFlow()很简单,就是获取限流规则,遍历调用canPassCheck()方法进行校验,如果校验失败,则抛出FlowException。

public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount,
                                boolean prioritized) {
    String limitApp = rule.getLimitApp();
    if (limitApp == null) {
        return true;
    }
    
    if (rule.isClusterMode()) {
        return passClusterCheck(rule, context, node, acquireCount, prioritized);
    }
    
    return passLocalCheck(rule, context, node, acquireCount, prioritized);
}
复制代码

canPassCheck()也很简单,就是根据不同的模式调用不同的方法。Sentinel可以使用集群模式运行或者本地模式,不同的模式限流逻辑不一样。这个地方由于没有讲到集群,所以先按本地模式进行分析。

 private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
                                      boolean prioritized) {
    Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
    if (selectedNode == null) {
        return true;
    }

    return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
}
复制代码

passLocalCheck()中,先根据配置的strategy和请求来源,获得对应的Node节点,再调用对应节点的canPass()方法进行校验。

在FlowRule中有一个TrafficShapingController,代表流量控制的类型,包括默认值、直接拒绝、Warm Up、匀速排队,所以具体如何限流需要看设置的策略。

4.4. TrafficShapingController

TrafficShapingController是一个接口,主要是定义流量控制策略,它有三个实现,分别代表不同的处理模式:

  • DefaultController:默认处理策略,直接拒绝处理
  • RateLimiterController:匀速排队
  • WarmUpController:预热/冷启动方式
  • WarmUpRateLimiterController:预热+匀速排队

在FlowRule中会有一个TrafficShapingController类型的变量rater,这个rater是在更新规则的时候根据设置的规则创建出来的,具体如下:

private static TrafficShapingController generateRater(/*@Valid*/ FlowRule rule) {
    if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {
        switch (rule.getControlBehavior()) {
            case RuleConstant.CONTROL_BEHAVIOR_WARM_UP:
                return new WarmUpController(rule.getCount(), rule.getWarmUpPeriodSec(),
                    ColdFactorProperty.coldFactor);
            case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER:
                return new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount());
            case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER:
                return new WarmUpRateLimiterController(rule.getCount(), rule.getWarmUpPeriodSec(),
                    rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor);
            case RuleConstant.CONTROL_BEHAVIOR_DEFAULT:
            default:
                // Default mode or unknown mode: default traffic shaping controller (fast-reject).
        }
    }
    return new DefaultController(rule.getCount(), rule.getGrade());
}
复制代码

5. 熔断降级

Sentinel熔断降级会在调用链路中某个资源出现不稳定状态的时候对该资源进行限制,让请求快速失败,避免影响其他的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口内,对该资源的调用都会自动熔断。

5.1. 降级策略

  • 平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。

  • 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

  • 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态

5.2. DegradeRule

public class DegradeRule extends AbstractRule {

    /**
     * RT threshold or exception ratio threshold count.
     */
    private double count;

    /**
     * Degrade recover timeout (in seconds) when degradation occurs.
     */
    private int timeWindow;

    /**
     * Degrade strategy (0: average RT, 1: exception ratio, 2: exception count).
     */
    private int grade = RuleConstant.DEGRADE_GRADE_RT;

    /**
     * 每秒钟连续进入的请求超的平均响应时间超过阀值的请求数
     * Minimum number of consecutive slow requests that can trigger RT circuit breaking.
     *
     * @since 1.7.0
     */
    private int rtSlowRequestAmount = RuleConstant.DEGRADE_DEFAULT_SLOW_REQUEST_AMOUNT;

    /**
     * 每秒钟连续进入的请求发生异常的请求数
     * Minimum number of requests (in an active statistic time span) that can trigger circuit breaking.
     *
     * @since 1.7.0
     */
    private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT;
}
复制代码

DeGradeRule代表熔断降级的规则,各个字段含义为:

  • grade:熔断降级的模式,有平均响应时间、异常比例、异常数。
  • count:发生熔断降级的阈值。
  • timeWindow:发生熔断降级后持续的时间。
  • minRequestAmount:每秒连续进入的请求发生异常不熔断降级的最小阈值。
  • rtSlowRequestAmount:每秒连续进入的请求平均响应时间超过阈值的数量。

5.3. DegradeSlot

DegradeSlot是负责处理熔断降级的功能插槽,其代码非常简单,因为具体熔断降级的判断是在DegradeRuleManager中实现的。

public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
    throws Throwable {
    DegradeRuleManager.checkDegrade(resourceWrapper, context, node, count);
    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
复制代码

5.4. DegradeRuleManager

DegradeRuleManager主要负责加载熔断降级规则、对调用entry进行熔断降级校验。

/**
 * 保存降级规则的缓存,以resource为key
 */
private static final Map<String, Set<DegradeRule>> degradeRules = new ConcurrentHashMap<>();

/**
 * 规则改变处理器
 */
private static final RulePropertyListener LISTENER = new RulePropertyListener();


private static SentinelProperty<List<DegradeRule>> currentProperty
    = new DynamicSentinelProperty<>();

static {
    currentProperty.addListener(LISTENER);
}
复制代码

DegradeRuleManager使用一个map来保存不同资源加载的熔断降级规则,并且也拥有自己的规则改变处理器,规则的加载和更新逻辑与其他功能插槽类似。

从上面可以看出,当entry进入DegradeSlot的时候,实际上是调用DegradeRuleManager的checkDegrade()方法进行熔断降级的检查。

 public static void checkDegrade(ResourceWrapper resource, Context context, DefaultNode node, int count)
    throws BlockException {

    Set<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(), rule);
        }
    }
}
复制代码

checkDegrade()方法先根据resource获取对应的限流规则,然后循环调用规则的passCheck()方法进行检查,如果不能通过检查,则抛出DegradeException。

所以,具体的校验逻辑是在DegradeRule中的passCheck()实现的,具体代码如下:

 public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) {
    //如果是在限流的窗口时间内,直接降级
    if (cut.get()) {
        return false;
    }

    //降级只针对resource维度进行,不区分context,不区分orgin,在statisticSlot中会在defaultNode里面对clusterNode进行累加
    ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(this.getResource());
    if (clusterNode == null) {
        return true;
    }

    if (grade == RuleConstant.DEGRADE_GRADE_RT) {

        //case1:rt降级
        //平均响应时间
        double rt = clusterNode.avgRt();

        //平均响应时间小于设置的阀值,直接通过
        if (rt < this.count) {
            passCount.set(0);
            return true;
        }
        
        //请求数自增,如果请求数小于设置的值5,直接通过
        if (passCount.incrementAndGet() < rtSlowRequestAmount) {
            return true;
        }
        
    } else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {

        //case2 异常比例

        double exception = clusterNode.exceptionQps();
        double success = clusterNode.successQps();
        double total = clusterNode.totalQps();
        
        //如果请求总数小于设置的每秒允许的最小请求数量,直接返回
        if (total < minRequestAmount) {
            return true;
        }
        double realSuccess = success - exception;
        if (realSuccess <= 0 && exception < minRequestAmount) {
            return true;
        }

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

        //case3: 异常数

        //统计异常总数的时间窗口是分钟级别的,如果timeWindow的时间小于60s,会以60s进行熔断
        
        double exception = clusterNode.totalException();
        //如果异常数小于指定的值,直接返回
        if (exception < count) {
            return true;
        }
    }

    //开启一个定时任务,在指定的窗口时间后执行,将请求数设置为0,降级标识设置为false
    if (cut.compareAndSet(false, true)) {
        ResetTask resetTask = new ResetTask(this);
        pool.schedule(resetTask, timeWindow, TimeUnit.SECONDS);
    }

    return false;
}
复制代码

passCheck()逻辑比较简单,主要是先根据之前获取的数据判断是否满足降级设置的阈值,如果超过,则返回false,并且开启一个线程,其作用就是发生降级后指定的timeWindow内直接将后续请求当做降级处理。

6. 小结

这篇文章主要分析Sentinel中常用的功能插槽的实现原理,了解Sentinel的限流降级策略。

7.参考资料

流量控制
熔断降级
系统自适应限流
黑白名单控制

猜你喜欢

转载自juejin.im/post/5df83b2af265da33ac2cf1b3