Sentinel Source resolve four (flow control and flow control policy effects)

Flow control and flow control policy effects

introduction

In Sentinel analysis of the previous article, we know it is based on a sliding window to do traffic statistics, then when we can get real-time data traffic based on traffic statistics algorithm, the next thing to do is based on these natural do data flow control. In the introduction Sentinelbefore the flow control model, we first take a brief look at the background Sentinel is how to define a flow control rules

For the configuration of FIG Sentinelit into an abstract FlowRuleclass, their attributes correspond

  • resource resource name
  • limitApp limiting sources, the default is default does not distinguish between sources
  • grade limiting the type and number of concurrent threads have QPS two types
  • count limit threshold
  • flow control strategy associated policy 3. 2. 1. Direct Link
  • 1. controlBehavior flow control effect rapid warm start failed 2. 3. 4. queued waiting warm start
  • warmUpPeriodSec flow control effect is long preheating preheating start (sec)
  • maxQueueingTimeMs flow control effect waiting duration (ms) when a queued

Here we look at selected flow control and flow control policy effects of the core code

private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,boolean prioritized) {
// 根据流控策略选择需要流控的Node维度节点                              
    Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
    if (selectedNode == null) {
        return true;
    }
    // 获取配置的流控效果 控制器 (1. 直接拒绝 2. 预热启动 3. 排队 4. 预热启动排队等待)
    return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
}

The code above is relatively simple processes is also very clear, first of all get to the appropriate dimensions in accordance with flow control policy we configured the Node node (Node node is the Sentinel traffic statistics basic unit), and then get into the flow control effect of the rules in the configuration of control is (1. direct 2. refused warm start 4. 3. queued waiting warm start).

Flow control policy

Here we look at the source code analysis to select flow control policy

static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) {
    // 获取限流来源 limitApp
    String limitApp = rule.getLimitApp();
    // 获取限流策略
    int strategy = rule.getStrategy();
    // 获取当前 上下文的 来源
    String origin = context.getOrigin();
    
    // 如果规则配置的限流来源 limitApp 等于 当前上下文来源
    if (limitApp.equals(origin) && filterOrigin(origin)) {
    // 且配置的流控策略是 直接关联策略
        if (strategy == RuleConstant.STRATEGY_DIRECT) {
            // 直接返回当前来源 origin 节点
            return context.getOriginNode();
        }
           // 配置的策略为关联或则链路
        return selectReferenceNode(rule, context, node);
        
        
    // 如果规则配置的限流来源 limitApp 等于 default 
    } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
    // 且配置的流控策略是 直接关联策略
        if (strategy == RuleConstant.STRATEGY_DIRECT) {
            // 直接返回当前资源的 clusterNode
            return node.getClusterNode();
        }
        // 配置的策略为关联或则链路
        return selectReferenceNode(rule, context, node);
        
        // 如果规则配置的限流来源 limitApp 等于 other,且当前上下文origin不在流控规则策略中
    } 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);
    }

    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;
    }
    // 策略是关联
    if (strategy == RuleConstant.STRATEGY_RELATE) {
    // 返回关联的资源ClusterNode
        return ClusterBuilderSlot.getClusterNode(refResource);
    }
    // 策略是链路
    if (strategy == RuleConstant.STRATEGY_CHAIN) {
    // 当前上下文名称不是规则配置的name 直接返回null
        if (!refResource.equals(context.getName())) {
            return null;
        }
        return node;
    }
    // No node.
    return null;
}

Logic to determine the code more, we take a look at the entire process management

  • LimitAppScope only flow control policy configured to RuleConstant.STRATEGY_DIRECTact when (direct correlation). It has three configurations, respectively default, origin_name,other
    • If the default configuration to default, represents the statistics do not distinguish between sources of current resources will be any source of traffic statistics (in fact, is to choose Node is clusterNode dimension)
    • origin_name if configured to specify the name of origin_name, traffic sources will only make the current configuration statistics
    • If configured for other other will enter into force for all other sources but does not include the source of the second configuration
  • When the policy is configured to RuleConstant.STRATEGY_RELATE or RuleConstant.STRATEGY_CHAIN
    • STRATEGY_RELATE associated with other designated resources, such as resource A resource B want to traffic conditions to determine whether to limit, then resources A rule configuration can be used STRATEGY_RELATE policy
    • STRATEGY_CHAIN ​​specified inlet flow rate limiting, since the flow rate can have a plurality of different inlets (EntranceNode)
  • For the previous few nodes above relationship is not clear you can see the beginning of my article overview map https://www.cnblogs.com/taromilk/p/11750962.html

Flow control effect

Configuring flow control on the effect of four, we look at their initialization code

/**
 * class com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil
 */
private static TrafficShapingController generateRater(/*@Valid*/ FlowRule rule) {
// 只有Grade为统计 QPS时 才可以选择除默认流控效果外的 其他流控效果控制器
    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());
}

You can be more clearly seen corresponding to a total of four flow controller initialization

Direct refusal

@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
// 获取当前qps
    int curCount = avgUsedTokens(node);
    // 判断是否已经大于阈值
    if (curCount + acquireCount > count) {
    // 如果当前流量具有优先级,则会提前去获取未来的通过资格
        if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {
            long currentTime;
            long waitInMs;
            currentTime = TimeUtil.currentTimeMillis();
            waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);
            if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {
                node.addWaitingRequest(currentTime + waitInMs, acquireCount);
                node.addOccupiedPass(acquireCount);
                sleep(waitInMs);

                // PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}.
                throw new PriorityWaitException(waitInMs);
            }
        }
        return false;
    }
    return true;
}

Such a strategy is relatively simple and crude, exceeding the threshold value will flow directly rejected. But here's a little detail, if the inlet flow prioritized is true, that is, high priority will be realized through future time window occupied places. In this last article has introduced to

Warm start

WarmUpControllerMainly used to prevent sudden increase in traffic, so this system can handle in a stable state, but because many resources are not warm, not a cause processing. Note here does not mean a one-time pre-warm-up after the system starts, but refers to any time the system is running low flow rate from the peak to the sudden increase in the warm-up phase .

Here we look at the WarmUpControllerconcrete implementation class


/**
 * WarmUpController 构造方法
 * @param count 当前qps阈值
 * @param warmUpPeriodInSec 预热时长 秒
 * @param coldFactor 冷启动系数 默认为3
 */
private void construct(double count, int warmUpPeriodInSec, int coldFactor) {

    if (coldFactor <= 1) {
        throw new IllegalArgumentException("Cold factor should be larger than 1");
    }

    this.count = count;

    this.coldFactor = coldFactor;

    // 剩余Token的警戒值,小于警戒值系统就进入正常运行期
    warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1);
    // 系统最冷时候的剩余Token数
    maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor));

    // 系统预热的速率(斜率)
    slope = (coldFactor - 1.0) / count / (maxToken - warningToken);

}

@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
    long passQps = (long) node.passQps();

    long previousQps = (long) node.previousPassQps();
    // 计算当前的 剩余 token 数
    syncToken(previousQps);

    // 如果进入了警戒线,开始调整他的qps
    long restToken = storedTokens.get();
    if (restToken >= warningToken) {
    // 计算剩余token超出警戒值的值
        long aboveToken = restToken - warningToken;
        // 计算当前允许通过的最大 qps
        double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
        if (passQps + acquireCount <= warningQps) {
            return true;
        }
    } else {
    // 不在预热阶段,则直接判断当前qps是否大于阈值
        if (passQps + acquireCount <= count) {
            return true;
        }
    }

    return false;
}

The first is the construction method, it focuses on two important parameters

  1. The remaining token guard value warningToken
  2. maxToken remaining maximum number of token, if the token is equal to the number of remaining maxToken, then the system is in the coldest stage

To understand the meaning of these parameters, reference may be token bucket algorithm, each through a request, it will take away a token from the token bucket. Imagine then, when the token bucket token maximum is reached, means that the system is not currently in the coldest stage, because the token bucket is always in a very saturated state. Here token is corresponding to the maximum maxToken, and warningTokenthen a corresponding warning value, while reducing the number of tokens in the bucket to a specified value, it indicates that the system had the preheating phase

When a request comes in, first need to calculate the number of remaining current bucket token, specific logic syncTokenprocess
when the system is greater than the remaining Token warningToken, described system is still in the preheating stage, so the need to adjust the maximum value of the current threshold can qps by

protected void syncToken(long passQps) {
    long currentTime = TimeUtil.currentTimeMillis();
    // 获取秒级别时间(去除毫秒)
    currentTime = currentTime - currentTime % 1000;
    long oldLastFillTime = lastFilledTime.get();
    if (currentTime <= oldLastFillTime) {
        return;
    }

    long oldValue = storedTokens.get();
    // 判断是否需要往桶中添加令牌
    long newValue = coolDownTokens(currentTime, passQps);
    // 设置新的token数
    if (storedTokens.compareAndSet(oldValue, newValue)) {
    // 如果设置成功的话则减去上次通过的qps数量,就得到当前的实际token数
        long currentValue = storedTokens.addAndGet(0 - passQps);
        if (currentValue < 0) {
            storedTokens.set(0L);
        }
        lastFilledTime.set(currentTime);
    }

}
  1. Get the current time
  2. The method determines whether coolDownTokens need to put a token bucket, and returns the latest token number
  3. If the return of the latest token number, the current number of remaining token minus qps has passed, get the latest number of remaining token
private long coolDownTokens(long currentTime, long passQps) {
    long oldValue = storedTokens.get();
    long newValue = oldValue;

    // 添加令牌的几种情况
    // 1. 系统初始启动阶段,oldvalue = 0,lastFilledTime也等于0,此时得到一个非常大的newValue,会取maxToken为当前token数量值
    // 2. 系统处于预热阶段 且 当前qps小于 count / coldFactor
    // 3. 系统处于完成预热阶段
    if (oldValue < warningToken) {
        newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
    } else if (oldValue > warningToken) {
        if (passQps < (int)count / coldFactor) {
            newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
        }
    }
    return Math.min(newValue, maxToken);
}

Here a look at several cases will add tokens

  1. Initial system startup phase, oldvalue = 0, lastFilledTime also equal to 0, this time to obtain a very large newValue, will be taken as the current token number of values ​​maxToken
  2. The system is complete warm-up phase, it is necessary to add a token to stabilize within the range
  3. The system is less than qps preheating stage and the current count / coldFactor

The first two cases is better understood, here to explain why the third case, why the current qpsis less than count / coldFactor, you need to add Token bucket? Imagine what would happen if Without this step, if this step is not to add Token at relatively low qps, the system will eventually slowly through the warm-up phase, but in fact such a low qps ( 小于 count / coldFactor时) should not complete the warm-up . So here it will be lower than in qps count / coldFactorsupplemented when the number of remaining token, to make the system in case of low qps always in the warm-up state

Waiting in line

Waiting in line to achieve a relatively warm start is relatively simple

First, we will pass arrangement, two adjacent calculated minimum time allowed by the request, and will record the time of the most recent pass. Two together is the next request that is allowed by the minimum time.

public boolean canPass(Node node, int acquireCount, boolean prioritized) {
    if (acquireCount <= 0) {
        return true;
    }
    if (count <= 0) {
        return false;
    }

    long currentTime = TimeUtil.currentTimeMillis();
    // 计算相隔两个请求 需要相隔多长时间
    long costTime = Math.round(1.0 * (acquireCount) / count * 1000);

    // 本次期望通过的最小时间
    long expectedTime = costTime + latestPassedTime.get();
    // 如果当前时间大于期望时间,说明qps还未超过阈值,直接通过
    if (expectedTime <= currentTime) {
       
        latestPassedTime.set(currentTime);
        return true;
    } else {
        // 当前时间小于于期望时间,请求过快了,需要排队等待指定时间
        
        // 计算等待时间
        long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();
        // 等待时长大于我们设置的最大时长,则不通过
        if (waitTime > maxQueueingTimeMs) {
            return false;
        } else {
        // 否则则排队等待,占用下通过时间
            long oldTime = latestPassedTime.addAndGet(costTime);
            try {
        
                waitTime = oldTime - TimeUtil.currentTimeMillis();
                // 判断等待时间是否已经大于最大值
                if (waitTime > maxQueueingTimeMs) {
                // 大于则将上一步加的值重新减去
                    latestPassedTime.addAndGet(-costTime);
                    return false;
                }
                // in race condition waitTime may <= 0
                // 占用等待时间成功,直接sleep costTime
                if (waitTime > 0) {
                    Thread.sleep(waitTime);
                }
                return true;
            } catch (InterruptedException e) {
            }
        }
    }
    return false;
}

Waiting controllers around the core strategy is actually latestPassedTimecarried out, latestPassedTimereferring to the last request through time, through latestPassedTime+ costTimeto do with comparing the current time to determine whether the current request by the request will not be preempted by the latestPassedTimetime , time until sleep to be adopted. Of course, we can also configure the maximum time waiting in line, to limit the number of requests currently waiting in line to pass.

Warm start waiting in line

Preheat waiting, WarmUpRateLimiterControllerimplementation class we find it inherited WarmUpController, this is the Sentinel after the 1.4 version added a new controller, in fact, a combination of warm-up and line up waiting for, the specific source that we do not do analysis.

Last words

SentinelTraffic control policy and the combined use is still very clever, among the flow control effects of some of the design is still very meaningful reference

Guess you like

Origin www.cnblogs.com/taromilk/p/11877242.html
Recommended