Sentinel 之所以针对每个资源统计访问来源的指标数据,也是为了实现对丰富的限流策略的支持。

因为每个调用来源服务对同一个资源的访问频率都是不同的,针对调用来源限流可限制并发量较高的来源服务的请求,而对并发量低的来源服务的请求可不限流,或者是对一些并没有那么重要的来源服务限流。

当两个资源之间具有资源争抢关系的时候,使用 STRATEGY_RELATE 调用关系限流策略可避免多个资源之间过度的对同一资源争抢。例如查询订单信息和用户下单两个分别读和写数据库订单表的资源。

Sentinel笔记-Flow流控规则_数据

 

 

 

限流处理器插槽:FlowSlot

FlowSlot 是实现限流功能的切入点,它作为 ProcessorSlot 插入到 ProcessorSlotChain 链表中,在 entry 方法中调用 Checker 去判断是否需要拒绝当前请求,如果需要拒绝请求则抛出 Block 异常。FlowSlot 的源码如下:

public class FlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
    private final FlowRuleChecker checker;
    public FlowSlot() {
        this(new FlowRuleChecker());
    }

   // 规则生产者,一个 Function
    private final Function<String, Collection<FlowRule>> ruleProvider = new Function<String, Collection<FlowRule>>() {
        // 参数为资源名称
        @Override
        public Collection<FlowRule> apply(String resource) {
            Map<String, List<FlowRule>> flowRules = FlowRuleManager.getFlowRuleMap();
            return flowRules.get(resource);
        }
    };

    @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);
    }
  // check 是否限流
    void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
        throws BlockException {
        checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
    }

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

 

 

限流规则检查器:FlowRuleChecker

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;
        }
        // (1)
        Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
        if (rules != null) {
            // (2)
            for (FlowRule rule : rules) {
                // (3)
                if (!canPassCheck(rule, context, node, count, prioritized)) {
                    throw new FlowException(rule.getLimitApp(), rule);
                }
            }
        }
}

public boolean canPassCheck(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);
}

 

 

passLocalCheck 方法源码如下:

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

   //根据流控策略找到对应的实时统计信息(Node)
    static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) {

        // 限流规则针对的来源
        // 如果当前限流规则的 limitApp 为 default,则说明该限流规则对任何调用来源都生效,针对所有调用来源限流,否则只针对指定调用来源限流。
        String limitApp = rule.getLimitApp();

        //基于调用关系的限流策略
        int strategy = rule.getStrategy();

        // 调用来源, ContextUtil.enter(res,origin) 时传入        String origin = context.getOrigin();

        //如果限流规则配置的针对的调用方与当前请求实际调用来源匹配(并且不是 default、other)时的处理逻辑
        if (limitApp.equals(origin) && filterOrigin(origin)) {

            // (origin==limitApp),直接使用OriginNode, 实现针对该origin来源限流。
            if (strategy == RuleConstant.STRATEGY_DIRECT) {
                // Matches limit origin, return origin statistic node.
                return context.getOriginNode();
            }

            return selectReferenceNode(rule, context, node);

            //如果流控规则针对的调用方(limitApp) 配置的为 default,表示对所有的调用源都生效.

        } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
            if (strategy == RuleConstant.STRATEGY_DIRECT) {
                // 直接返回 cluster node.
                return node.getClusterNode();
            }

            return selectReferenceNode(rule, context, node);

            //如果流控规则针对的调用方为(other),此时需要判断是否有针对当前的流控规则,只要存在,则这条规则对当前资源“失效”,
            //如果 limitApp 为 other,且该资源的所有限流规则都没有针对当前的Origin限流, 这使用本originNode
            //如果针对该资源没有配置其他额外的流控规则.
        } else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
                && FlowRuleManager.isOtherOrigin(origin, rule.getResource())) {
            if (strategy == RuleConstant.STRATEGY_DIRECT) {
                return context.getOriginNode();
            }

            return selectReferenceNode(rule, context, node);
        }

        //都未匹配,表示没有找到Statistics Node,则直接通过.
        return null;
    }

    static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) {
        String refResource = rule.getRefResource();
        int strategy = rule.getStrategy();

        if (StringUtil.isEmpty(refResource)) {
            return null;
        }

        //关联模式, 从集群环境中获取对应关联资源所代表的 Node
        //通俗点说就是使用其它资源的指标数据(statisticsNode)匹配当前的rule,
        //如果你的并发量高,到了我的规则值,我就限流,等你并发量降低到我的rule以下,我就不限流了;
        if (strategy == RuleConstant.STRATEGY_RELATE) {
            return ClusterBuilderSlot.getClusterNode(refResource);
        }

        // 判断当前调用上下文的入口资源与规则配置的是否一样,
        // 如果是,则返回入口资源对应的Node,即当前DefaultNode
        // 否则返回 null,表示该条流控规则,不参与流控判断.
        if (strategy == RuleConstant.STRATEGY_CHAIN) {
            if (!refResource.equals(context.getName())) {
                return null;
            }
            return node;
        }
        // No node.
        return null;
    }
  private static boolean filterOrigin(String origin) {
        //origin 不能为 default 和 other
        return !RuleConstant.LIMIT_APP_DEFAULT.equals(origin) && !RuleConstant.LIMIT_APP_OTHER.equals(origin);
    }
 

 

 

简而言之:

 
验证流程:就是逐个遍历Resource对应的Rule,拿Rule_limitApp去匹配来源origin(来源不能为default和other)
  1. 如果匹配上(limit_app==Origin),验证流控模式
    1. 直接流控:取origin_statistics_node
    2. 关联流控:通俗点说就是使用其它res的指标数据(statisticsNode)匹配当前的rule, 如果你的并发量高,到了我的规则值,我就限流,等你并发量降低到我的rule以下,我就不限流了;
    3. 链路模式:判断当前调用上下文的入口资源与规则配置的是否一样,如果是,则返回入口资源对应的Node,即当前DefaultNode,否则直接pass
  2. 如果匹配不上,验证流控模式
    1. 如果Rule_limitApp为Deafult,
      1. 直接流控:取Cluster_statistics_node
      2. 关联流控:通俗点说就是使用其它res的指标数据(statisticsNode)匹配当前的rule, 如果你的并发量高,到了我的规则值,我就限流,等你并发量降低到我的rule以下,我就不限流了;
      3. 链路模式:判断当前调用上下文的入口资源与规则配置的是否一样,如果是,则返回入口资源对应的Node,即当前DefaultNode,否则直接pass
    2. 如果Rule_limitApp为Other,并且该Res下没有对origin做单独规则,
      1. 直接流控:取origin_statistics_node
      2. 关联流控:通俗点说就是使用其它res的指标数据(statisticsNode)匹配当前的rule, 如果你的并发量高,到了我的规则值,我就限流,等你并发量降低到我的rule以下,我就不限流了;
      3. 链路模式:判断当前调用上下文的入口资源与规则配置的是否一样,如果是,则返回入口资源对应的Node,即当前DefaultNode,否则直接pass
    3. 标识规则没匹配上,直接pass 

 

从 selectNodeByRequesterAndStrategy 方法可以看出,Sentinel 之所以针对每个Resource统计访问来源的指标数据,也是为了实现对丰富的限流策略的支持。

因为每个调用来源服务对同一个资源的访问频率都是不同的,针对调用来源限流可限制并发量较高的来源服务的请求,而对并发量低的来源服务的请求可不限流,或者是对一些并没有那么重要的来源服务限流。

当两个资源之间具有资源争抢关系的时候,使用 STRATEGY_RELATE 调用关系限流策略可避免多个资源之间过度的对同一资源争抢。