标签:条件 hat 统计信息 iba bsp iter boolean 应用程序 uil
Sentinel 触发限流的实现类为 FlowSlot。我们再来简单思考一下,要实现触发限流,至少需要完成如下几件事情:
如何收集实时调用信息在前面的文章中已详细介绍,请带着上述问题开始本节的探讨。
我们先从 FlotSlot 类的注释来简单认识一下流量控制相关的内容。
FlowSlot 的类图非常简单,内部持有一个成员变量,FlowRuleChecker,用来判断是否满足流控触发条件。
在继续探讨 Sentinel 限流之前,我们先来了解一下 FlowRule,即认识一下 Sentinel 流控规则主要包含哪些配置项,为后续的流程做一个消息的准备。
FlowRule 的类体系如图所示:
其属性的含义如下:
FlowSlot#entry
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { // @1 checkFlow(resourceWrapper, context, node, count, prioritized); // @2 fireEntry(context, resourceWrapper, node, count, prioritized, args); // @3 }
代码@1:首先来解释一下该方法的参数:
代码@2:调用 checkFlow ,根据配置的限流规则,结合实时统计信息,判断是否满足流控条件,如果满足,则触发流控,稍后会详细探讨该方法的实现原理。
代码@3:调用 fireEntry 继续沿着 slot 链进行传播。
FlowSlot 的 checkFlow 方法在内部就是直接调用 FlowRuleChecker 的 checkFlow 方法,故我们将目光放到 FlowRuleChecker 中。
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()); // @1 if (rules != null) { for (FlowRule rule : rules) { if (!canPassCheck(rule, context, node, count, prioritized)) { // @2 throw new FlowException(rule.getLimitApp(), rule); } } } }
代码@1:通过限流规则提供器获取与该资源相关的流控规则列表。
代码@2:然后遍历流控规则列表,通过调用 canPassCheck 方法来判断是否满足该规则设置的条件,如果满足流控规则,则抛出 FlowException,即只需要满足一个即结束校验。
接下来继续查看 canPassCheck 方法。
public boolean canPassCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, boolean prioritized) { String limitApp = rule.getLimitApp(); if (limitApp == null) { // @1 return true; } if (rule.isClusterMode()) { // @2 return passClusterCheck(rule, context, node, acquireCount, prioritized); } return passLocalCheck(rule, context, node, acquireCount, prioritized); }
代码@1:如果限流规则没有配置针对来源,则直接默认通过,该值在配置时,默认为 default,即对所有调用发起方都生效。
代码@2:如果是集群限流模式,则调用 passClusterCheck,非集群限流模式则调用 passLocalCheck 方法,本文重点讲述单节点限流,集群限流模式将在后续文章中详细探讨。
FlowRuleChecker#passLocalCheck
private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, boolean prioritized) { Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node); // @1 if (selectedNode == null) { return true; } return rule.getRater().canPass(selectedNode, acquireCount, prioritized); // @2 }
代码@1:首先根据流控模式(strategy)选择一个合适的 Node,看到这,大家可以思考一下,这一步骤的目的,如果为空,则直接返回 true,表示放行。
代码@2:调用 FlowRule 内部持有的流量控制器来判断是否符合流控规则,最终调用的是 TrafficShapingController canPass 方法。
那我们接下来分别对上述两个方法进行详细展开。
FlowRuleChecker#selectNodeByRequesterAndStrategy static Node selectNodeByRequesterAndStrategy(FlowRule rule, Context context, DefaultNode node) { String limitApp = rule.getLimitApp(); int strategy = rule.getStrategy(); String origin = context.getOrigin(); // @1 if (limitApp.equals(origin) && filterOrigin(origin)) { // @2 if (strategy == RuleConstant.STRATEGY_DIRECT) { // Matches limit origin, return origin statistic node. return context.getOriginNode(); } return selectReferenceNode(rule, context, node); } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) { // @3 if (strategy == RuleConstant.STRATEGY_DIRECT) { // Return the cluster node. return node.getClusterNode(); } return selectReferenceNode(rule, context, node); } else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp) && FlowRuleManager.isOtherOrigin(origin, rule.getResource())) { // @4 if (strategy == RuleConstant.STRATEGY_DIRECT) { return context.getOriginNode(); } return selectReferenceNode(rule, context, node); } return null; }
在介绍该方法之前,先回答上文提到一个问题,我们知道,要判断是否满足了限流规则所配置的条件,一个重要的点就是要拿到当前的实时统计信息,通过上面介绍限流规则时提到 Sentinel 目前支持3种流控模式(直接、关联、链路),针对模式的不同,选择的实时统计数据的逻辑就应该不同,即该方法主要是根据流控策略找到对应的实时统计信息(Node)。
代码@1:首先先介绍几个局部变量的含义:
代码@2:如果限流规则配置的针对的调用方与当前请求实际调用来源匹配(并且不是 default、other)时的处理逻辑,其实现的要点:
代码@3:如果流控规则针对的调用方(limitApp) 配置的为 default,表示对所有的调用源都生效,其获取实时统计节点(Node)的处理逻辑为:
代码@4:如果流控规则针对的调用方为(other),此时需要判断是否有针对当前的流控规则,只要存在,则这条规则对当前资源“失效”,如果针对该资源没有配置其他额外的流控规则,则获取实时统计节点(Node)的处理逻辑为:
从这里可以看出,流控规则针对调用方如果设置为 other,表示针对没有配置流控规则的资源。
根据流控策略选择合适的 Node 的逻辑就介绍到这里,如果没有选择到合适的 Node,则针对该流控规则,默认放行。
经过上一个步骤获取到对应的实时统计数据,接下来就是根据数据与流控规则,是否匹配。Sentinel 中用于实现流控规则的匹配其类体系如图所示:
由于篇幅的关系,本节只会以 DefaultController 来介绍其实现原理,对应【流控模式:快速失败】,由于篇幅的关系,其他两种流控模式将在下文详细探讨。
DefaultController#canPass
public boolean canPass(Node node, int acquireCount, boolean prioritized) { int curCount = avgUsedTokens(node); // @1 if (curCount + acquireCount > count) { // @2 if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) { // @3 long currentTime; long waitInMs; currentTime = TimeUtil.currentTimeMillis(); waitInMs = node.tryOccupyNext(currentTime, acquireCount, count); // @4 if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) { // @5 node.addWaitingRequest(currentTime + waitInMs, acquireCount); node.addOccupiedPass(acquireCount); sleep(waitInMs); // @6 // PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}. throw new PriorityWaitException(waitInMs); // @7 } } return false; // @8 } return true; // @9 }
代码@1:首先先解释一下两个局部变量的含义:
代码@2:如果当前请求的令牌数加上已消耗的令牌数之和小于总令牌数,则直接返回true,表示通过,见代码@9;如果当前时间窗口剩余令牌数小于需要申请的令牌数,则需要根据是否有优先级进行不同的处理。
代码@4:尝试抢占下一个滑动窗口的令牌,并返回该时间窗口所剩余的时间,如果获取失败,则返回 OccupyTimeoutProperty.getOccupyTimeout() 值,该返回值的作用就是当前申请资源的线程将 sleep(阻塞)的时间。
代码@5:如果 waitInMs 小于抢占的最大超时时间,则在下一个时间窗口中增加对应令牌数,并且线程将sleep,见代码@6。
代码@7:这里不是很明白为什么等待 waitMs 之后,还需要抛出 PriorityWaitException,那这个prioritized 机制、可抢占下一个时间窗口的令牌有什么意义呢?应该是一个BUG吧。
整个 FlowSlot 限流规则就介绍到这里了,为了更加直观的认识其限流的流程,下面给出一张流程图来对上面的源码分析进行一个总结。
该篇注重理论与实践相结合,在进行源码解读之前先从流控规则配置界面入手,代入感比较强,文章再提供一张流程图。
整个限流部分目前还有所欠缺的两个部分:
1、流程规则的存储与加载。
2、其他几种流控后行为(预热、匀速排队等实现原理)
Alibaba-技术专区-Sentinel FlowSlot 限流实现原理
标签:条件 hat 统计信息 iba bsp iter boolean 应用程序 uil
原文地址:https://www.cnblogs.com/liboware/p/12503534.html