打开 Sentinel
官方文档的介绍,概括性的一句话便是:
Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
主要以流量为切入点,流量控制是这个框架的一个重要功能,因此本文结合框架wiki 文档,对流量控制的相关方法源码进行简单的整理说明。
Sentinel
:V1.8.6
对于概述部分,wiki 文档进行了很好的说明:
wiki 下文主要介绍了两种流量控制的方式:
本文流量控制的入口方法位于 FlowSlot
,按照方法调用的先后顺序,先说明 基于调用关系的流量控制
,再对 基于 QPS/并发数 的流量控制
进行展开。
基于调用关系的流量控制主要有三种:
RuleConstant.STRATEGY_DIRECT
RuleConstant.STRATEGY_CHAIN
RuleConstant.STRATEGY_RELATE
从方法 FlowSlot#checkFlow
一路深入,看看如何选择不同的流控模式。
该方法根据资源名称获取所有的限流规则,canPassCheck
循环判断是否通过。
FlowRule
在方法 FlowRuleChecker#checkFlow
中,从 ruleProvider
获取流量规则集合:
Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
ruleProvider
是一个函数变量,是一个实现了 Function
接口的匿名内部类,并重写了其中的 apply
方法,用于根据资源名称获取到对应的流控规则列表。
流控规则可以自定义配置,通过监听器来更新。
FlowRuleManager.FlowPropertyListener
FlowRuleChecker#selectNodeByRequesterAndStrategy
根据请求方法和限流策略选择节点:
limitApp
、策略 strategy
和请求方信息 origin
RuleConstant.STRATEGY_DIRECT
:返回 origin statistic node 源节点selectReferenceNode
RuleConstant.STRATEGY_DIRECT
:返回 cluster node 簇点selectReferenceNode
RuleConstant.STRATEGY_DIRECT
:返回 origin statistic node 源节点selectReferenceNode
FlowRuleChecker#selectReferenceNode
选择参考节点:
RuleConstant.STRATEGY_RELATE
(关联模式):ClusterBuilderSlot.getClusterNode(refResource)
簇点RuleConstant.STRATEGY_CHAIN
(链路模式):下面主要是QPS流量控制的三种方式:
流控通用接口 TrafficShapingController
:
在构建限流规则集合时,底层方法如下:
根据不同的规则,有不同的限流方式,下面按照顺序来说明。
直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
该方法的主要逻辑:
PriorityWaitException
异常,表示请求将在等待一段时间后通过。该方法主要是尝试占用令牌,入参为当前时间、希望获取的令牌数量、阈值,返回下一次尝试占用令牌的等待时间。主要逻辑如下:
threshold
是一个百分比,表示每秒允许占用的最大令牌数量。时间间隔 IntervalProperty.INTERVAL
是一个固定的时间段,用于计算令牌的窗口长度。double maxCount = threshold * IntervalProperty.INTERVAL / 1000;
long currentBorrow = rollingCounterInSecond.waiting();
int windowLength = IntervalProperty.INTERVAL / SampleCountProperty.SAMPLE_COUNT;
long earliestTime = currentTime - currentTime % windowLength + windowLength - IntervalProperty.INTERVAL;
然后,初始化一个索引变量 idx
,用于迭代时间窗口。接下来,获取当前通过的令牌数量。
(注释部分)注意,由于调用了rollingCounterInSecond.pass()方法之后可能经过了一段时间,所以当前通过的令牌数量可能小于实际通过的令牌数量。在高并发的情况下,这段代码可能导致更多的令牌被借用。
然后,进入一个循环,直到最早的时间点大于当前时间为止。在每次循环中,计算等待的时间。
long waitInMs = idx * windowLength + windowLength - currentTime % windowLength;
long windowPass = rollingCounterInSecond.getWindowPass(earliestTime);
该方法的主要逻辑:
syncToken
方法同步令牌。long passQps = (long) node.passQps();
long previousQps = (long) node.previousPassQps();
syncToken(previousQps);
syncToken
方法用于更新令牌桶中的令牌数量。首先,获取当前时间,并将其取整到秒。然后,获取上一次填充令牌的时间。如果当前时间小于等于上一次填充令牌的时间,则直接返回。
接着,获取令牌桶中的旧令牌数量,并根据当前时间和通过QPS计算新的令牌数量。
long newValue = coolDownTokens(currentTime, passQps);
使用 compareAndSet
方法将新的令牌数量设置到令牌桶中,并更新令牌桶中的当前令牌数量。如果当前令牌数量小于0,则将其设置为0。最后,更新上一次填充令牌的时间。
然后,根据令牌桶中的令牌数量和希望占用的令牌数量进行判断,如果令牌桶中的令牌数量大于等于警戒线以上的令牌数量,即令牌消耗程度较低,那么根据令牌消耗速率和令牌数量计算警戒线以下的 QPS,并判断通过 QPS 加上希望占用的令牌数量是否小于等于警戒线以下的 QPS。如果是,则返回 true,表示节点可以通过。
如果令牌桶中的令牌数量小于警戒线以上的令牌数量,即令牌消耗程度较高,那么根据通过 QPS 和冷却因子的比较判断是否需要添加令牌。如果通过 QPS 小于总令牌数量除以冷却因子,即令牌消耗速率较低,那么根据令牌消耗速率和令牌数量计算新的令牌数量。最后,返回新的令牌数量和最大令牌数量的较小值。
返回 false
该方法的主要逻辑:
acquireCount
是否小于等于0,如果是的话,直接返回 true,表示可以通过。long costTime = Math.round(1.0 * (acquireCount) / count * 1000);
long expectedTime = costTime + latestPassedTime.get();
long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();
(完)