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); } } } } 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)
- 如果匹配上(limit_app==Origin),验证流控模式
- 直接流控:取origin_statistics_node
- 关联流控:通俗点说就是使用其它res的指标数据(statisticsNode)匹配当前的rule, 如果你的并发量高,到了我的规则值,我就限流,等你并发量降低到我的rule以下,我就不限流了;
- 链路模式:判断当前调用上下文的入口资源与规则配置的是否一样,如果是,则返回入口资源对应的Node,即当前DefaultNode,否则直接pass
- 如果匹配不上,验证流控模式
- 如果Rule_limitApp为Deafult,
- 直接流控:取Cluster_statistics_node
- 关联流控:通俗点说就是使用其它res的指标数据(statisticsNode)匹配当前的rule, 如果你的并发量高,到了我的规则值,我就限流,等你并发量降低到我的rule以下,我就不限流了;
- 链路模式:判断当前调用上下文的入口资源与规则配置的是否一样,如果是,则返回入口资源对应的Node,即当前DefaultNode,否则直接pass
- 如果Rule_limitApp为Other,并且该Res下没有对origin做单独规则,
- 直接流控:取origin_statistics_node
- 关联流控:通俗点说就是使用其它res的指标数据(statisticsNode)匹配当前的rule, 如果你的并发量高,到了我的规则值,我就限流,等你并发量降低到我的rule以下,我就不限流了;
- 链路模式:判断当前调用上下文的入口资源与规则配置的是否一样,如果是,则返回入口资源对应的Node,即当前DefaultNode,否则直接pass
- 标识规则没匹配上,直接pass
从 selectNodeByRequesterAndStrategy 方法可以看出,Sentinel 之所以针对每个Resource统计访问来源的指标数据,也是为了实现对丰富的限流策略的支持。
因为每个调用来源服务对同一个资源的访问频率都是不同的,针对调用来源限流可限制并发量较高的来源服务的请求,而对并发量低的来源服务的请求可不限流,或者是对一些并没有那么重要的来源服务限流。
当两个资源之间具有资源争抢关系的时候,使用 STRATEGY_RELATE 调用关系限流策略可避免多个资源之间过度的对同一资源争抢。