• Sentinel笔记-Flow流控规则


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

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

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

      

    限流处理器插槽: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);
                    }
                }
            }
    }
    
    //canPassCheck 方法返回 true 说明允许请求通过,反之则不允许通过,
    //其中check分为local check和 clusterCheck.
    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包含三个关键步骤

    1. 根据调用来源和“调用关系限流策略”选择 DefaultNode
    2. 获取限流规则配置的流量效果控制器(TrafficShapingController);
    3. 调用 TrafficShapingController#canPass 方法完成 canPassCheck。

    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 调用关系限流策略可避免多个资源之间过度的对同一资源争抢。

    流量效果控制器:TrafficShapingController

    Sentinel 支持对超出限流阈值的流量采取效果控制器控制这些流量,流量效果控制支持:直接拒绝、Warm Up(冷启动)、匀速排队。

    对应 FlowRule 中的 controlBehavior 字段。在调用 FlowRuleManager#loadRules 方法时,FlowRuleManager 会将限流规则配置的 controlBehavior 转为对应的 TrafficShapingController。

    controlBehavior 的取值与使用的 TrafficShapingController 对应关系如下表格所示:

    DefaultController

    DefaultController 是默认使用的流量效果控制器,直接拒绝超出阈值的请求。当 QPS 超过限流规则配置的阈值,新的请求就会被立即拒绝,抛出 FlowException。

        @Override
        public boolean canPass(Node node, int acquireCount, boolean prioritized) {
            // (1) 
            int curCount = avgUsedTokens(node);
            // (2)
            if (curCount + acquireCount > count) {
                // (3)
                if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {
                    long currentTime;
                    long waitInMs;
                    currentTime = TimeUtil.currentTimeMillis();
                    // (4)
                    waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);
                    // (5)
                    if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {
                        // 将休眠之后对应的时间窗口的 pass(通过)这项指标数据的值加上 acquireCount
                        node.addWaitingRequest(currentTime + waitInMs, acquireCount);
                        // 添加占用未来的 pass 指标的数量
                        node.addOccupiedPass(acquireCount);
                        // 休眠等待,当前线程阻塞
                        sleep(waitInMs);
                        // 抛出 PriorityWait 异常,表示当前请求是等待了 waitInMs 之后通过的
                        throw new PriorityWaitException(waitInMs);
                    }
                }
                return false;
            }
            return true;
        }
    1. avgUsedTokens 方法:返回 node 当前时间窗口统计的QPS/ThreadCount,注意如果是ClusterNode的话,需要汇总所有Childer的QPS
    2. 如果将当前请求放行会超过限流阈值,且不满足(3),则直接拒绝当前请求。
    3. 如果限流阈值类型为 QPS,表示具有优先级的请求可以占用未来时间窗口的统计指标。
    4. 如果可以占用未来时间窗口的统计指标,则 tryOccupyNext 返回当前请求需要等待的时间,单位毫秒。
    5. 如果休眠时间在限制可占用的最大时间范围内,则挂起当前请求,当前线程休眠 waitInMs 毫秒。休眠结束后抛出 PriorityWait 异常,表示当前请求是等待了 waitInMs 之后通过的。

    RateLimiterController

    Sentinel 匀速流控效果是漏桶算法结合虚拟队列等待机制实现的,可理解为存在一个虚拟的队列,请求在队列中排队通过,每(count/1000)毫秒可通过一个请求,

    要配置限流规则使用匀速通过效果控制器 RateLimiterController,则必须配置限流阈值类型为 GRADE_QPS,并且阈值要少于等于 1000

    匀速流控适合用于请求突发性增长后剧降的场景。例如用在有定时任务调用的接口,在定时任务执行时请求量一下子飙高,但随后又没有请求的情况,这个时候我们不希望一下子让所有请求都通过,避免把系统压垮,但也不想直接拒绝超出阈值的请求,这种场景下使用匀速流控可以将突增的请求排队到低峰时执行,起到“削峰填谷”的效果。

    WarmUpController

    Warm Up,冷启动。在应用升级重启时,应用自身需要一个预热的过程,预热之后才能到达一个稳定的性能状态。Sentinel 冷启动限流算法参考了 Guava 的 SmoothRateLimiter 实现的冷启动限流算法。具体实现不做过多分析。

  • 相关阅读:
    插入排序和顺序查找,折半查找
    单链表有环判断问题解决办法
    INT_MAX和INT_MAX
    最简单的学习往往是最无效的
    基于Windows安装Mysql数据库
    禁止跨域_五分钟带你了解跨域
    搞定SpringBoot多数据源(2):动态数据源
    API网关(API GATEWAY)是什么?有什么作用?
    一文搞懂蓝绿发布、灰度发布和滚动发布
    第九篇 bootstrap实例
  • 原文地址:https://www.cnblogs.com/snow-man/p/15505504.html
Copyright © 2020-2023  润新知