【sentinel】Sentinel工作主流程以流控规则源码分析

news2024/10/5 14:16:44

Sentinel工作主流程

在Sentinel里面,所有的资源都对应一个资源名称(resourceName),每次资源调用都会创建一个Entry对象。Entry可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用SphU API显式创建。Entry创建的时候,同时也会创建一系列功能插槽(slot chain),这些插槽有不同的职责,例如:

  • NodeSelectorSlot:负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用链路来限流降级;
  • ClusterBuilderSlot:则用于存储资源的统计信息以及调用者信息,例如该资源的RT、QPS、thread count等等,这些信息将用作为多维度限流,降级的依据;
  • StatisticSlot:则用于记录、统计不同维度的runtime指标监控信息;
  • AuthoritySlot:则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
  • SystemSlot:则通过系统的状态,例如load等,来控制总的入口流量;
  • ParamFlowSlot:统计热点参数的调用量,并依据这些统计信息进行热点参数限流;
  • FlowSlot:则用于根据预设的限流规则以及前面slot统计的状态,来进行流量控制;
  • DegradeSlot:则通过统计信息以及预设的规则,来做熔断降级;


Sentinel将ProcessorSlot作为SPI接口进行扩展,使得SlotChain具备了扩展的能力。您可以自行加入自定义的slot并编排 slot间的顺序,从而可以给Sentinel添加自定义的功能。

几种Node的区别与联系

DefaultNode

DefaultNode的实例位于NodeSelectorSlot中,意味着一个资源对应一个NodeSelectorSlot,一个NodeSelectorSlot有多个DefaultNode。

public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {

    /**
     * {@link DefaultNode}s of the same resource in different context.
     */
    // key为contextName
    // 如果web-context-unify=false,contextName为sentinel_spring_web_context
    // 如果web-context-unify=true,contextName为url
    private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);

DefaultNode用于流控规则中的根据调用链路限流。

ClusterNode

ClusterNode是在ClusterBuilderSlot创建的,创建完后会放入DefaultNode中,这样当DefaultNode的统计数据更新时,ClusterNode的统计数据也跟着更新了。

public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    // key为资源,跟资源名直接绑定
    private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>();

clusterNodeMap是一个静态的Map结构,这样可以根据资源名查找到这个ClusterNode。

ClusterNode主要用于流控规则中的根据关联关系限流。

OriginNode

OriginNode对应的类型为StatisticNode,位于ClusterNode内部,当origin不为空时,OriginNode就会被创建,主要用于根据流控规则中的根据调用方限流。

public class ClusterNode extends StatisticNode {
... ...
    // key为orgin的名称
    private Map<String, StatisticNode> originCountMap = new HashMap<>();

EntranceNode

EntranceNode的类型为ClusterNode,统计的是整个实例的QPS和线程数等信息,主要用于系统规则的限流。

public final class Constants {
... ...
    /**
     * Global statistic node for inbound traffic. Usually used for {@code SystemRule} checking.
     */
    public final static ClusterNode ENTRY_NODE = new ClusterNode(TOTAL_IN_RESOURCE_NAME, ResourceTypeConstants.COMMON);

流控规则源码分析

程序的入口

不管sentinel与哪个框架整合,在外面包了多少层封装,程序的入口都在SphU#entry,都会调用到这个核心方法来,这是一个静态方法,所以只有在第一次调用的时候才会进行初始化,dashboard才会有数据展示,其他监控和api才会有数据展示。

com.alibaba.csp.sentinel.SphU#entry(java.lang.String, int, com.alibaba.csp.sentinel.EntryType)

public static Entry entry(String name, int resourceType, EntryType trafficType) throws BlockException {
    return Env.sph.entryWithType(name, resourceType, trafficType, 1, OBJECTS0);
}

public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized, Object[] args) throws BlockException {
    StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
    return entryWithPriority(resource, count, prioritized, args);
}

private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) throws BlockException {
    Context context = ContextUtil.getContext();
    if (context instanceof NullContext) {
        // The {@link NullContext} indicates that the amount of context has exceeded the threshold,
        // so here init the entry only. No rule checking will be done.
        return new CtEntry(resourceWrapper, null, context);
    }

    // 可在外部手动调用InternalContextUtil.internalEnter创建context,并指定origin
    if (context == null) {
        // Using default context.
        // 创建Context
        context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
    }

    // Global switch is close, no rule checking will do.
    if (!Constants.ON) {
        return new CtEntry(resourceWrapper, null, context);
    }

    // 根据资源名查询 处理器插槽责任链
    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

    /*
     * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
     * so no rule checking will be done.
     */
    if (chain == null) {
        return new CtEntry(resourceWrapper, null, context);
    }

    Entry e = new CtEntry(resourceWrapper, chain, context);
    try {
        // 调用责任链
        /**
         * @see DefaultProcessorSlotChain#entry(com.alibaba.csp.sentinel.context.Context, com.alibaba.csp.sentinel.slotchain.ResourceWrapper, java.lang.Object, int, boolean, java.lang.Object...)
         */
        chain.entry(context, resourceWrapper, null, count, prioritized, args);
    } catch (BlockException e1) {
        e.exit(count, args);
        throw e1;
    } catch (Throwable e1) {
        // This should not happen, unless there are errors existing in Sentinel internal.
        RecordLog.info("Sentinel unexpected exception", e1);
    }
    return e;
}

责任链的构建

lookProcessChain()会根据资源从缓存中查责任链是否已经存在了,不存在就调用SlotChainProvider.newSlotChain()进行创建。

com.alibaba.csp.sentinel.CtSph#lookProcessChain

ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
    ProcessorSlotChain chain = chainMap.get(resourceWrapper);
    if (chain == null) {
        synchronized (LOCK) {
            chain = chainMap.get(resourceWrapper);
            if (chain == null) {
                // Entry size limit.
                if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
                    return null;
                }

                // 初始化责任链
                chain = SlotChainProvider.newSlotChain();
                Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
                    chainMap.size() + 1);
                newMap.putAll(chainMap);
                newMap.put(resourceWrapper, chain);
                chainMap = newMap;
            }
        }
    }
    return chain;
}

newSlotChain()使用SPI机制加载SlotChainBuilder,我们可以自己注入SlotChainBuilder来实现自己的责任链,例如可以改变责任链的顺序等。

com.alibaba.csp.sentinel.slotchain.SlotChainProvider#newSlotChain

public static ProcessorSlotChain newSlotChain() {
    if (slotChainBuilder != null) {
        return slotChainBuilder.build();
    }

    // Resolve the slot chain builder SPI.
    /**
     * sentinel-core DefaultSlotChainBuilder
     * @see com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder
     */
    slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();

    if (slotChainBuilder == null) {
        // Should not go through here.
        RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
        slotChainBuilder = new DefaultSlotChainBuilder();
    } else {
        RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}",
                slotChainBuilder.getClass().getCanonicalName());
    }
    // 构建责任链
    return slotChainBuilder.build();
}

DefaultSlotChainBuilder#build()负责整个责任链的构建,也是通过SPI机制加载所有的ProcessorSlot,我们如果要增加ProcessorSlot,直接利用SPI进行注入即可,ProcessorSlot的执行顺序根据ProcessorSlot上的@Spi注解来指定,@Spi中的顺序值越小越先执行。

com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder#build

public ProcessorSlotChain build() {
    ProcessorSlotChain chain = new DefaultProcessorSlotChain();

    // SPI加载所有的ProcessorSlot
    // sentinel-core中配置的
    // 从这里可以看出每个资源对应一个ProcessorSlotChain,
    // ProcessorSlotChain中有多个ProcessorSlot,这些实例都跟资源绑定,不是单例
    /**
     *
     * # Sentinel default ProcessorSlots
     *
     * public static final int ORDER_NODE_SELECTOR_SLOT = -10000;
     * public static final int ORDER_CLUSTER_BUILDER_SLOT = -9000;
     * public static final int ORDER_LOG_SLOT = -8000;
     * public static final int ORDER_STATISTIC_SLOT = -7000;
     * public static final int ORDER_AUTHORITY_SLOT = -6000;
     * public static final int ORDER_SYSTEM_SLOT = -5000;
     * public static final int ORDER_FLOW_SLOT = -2000;
     * public static final int ORDER_DEGRADE_SLOT = -1000;
     *
     * ParamFlowSlot由sentinel-parameter-flow-control因引入
     *
     * @see com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot
     * @see com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot
     * @see com.alibaba.csp.sentinel.slots.logger.LogSlot
     * @see com.alibaba.csp.sentinel.slots.statistic.StatisticSlot
     * @see com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot
     * @see com.alibaba.csp.sentinel.slots.system.SystemSlot
     * @see com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot
     * @see com.alibaba.csp.sentinel.slots.block.flow.FlowSlot
     * @see com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot
     */
    List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
    for (ProcessorSlot slot : sortedSlotList) {
        if (!(slot instanceof AbstractLinkedProcessorSlot)) {
            RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
            continue;
        }

        chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
    }

    return chain;
}

默认的责任链如下图:

责任链的执行

责任链的第一个Slot是一个AbstractLinkedProcessorSlot类型的匿名内部类,调用父类的fireEntry()向后传递。

com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain#entry

AbstractLinkedProcessorSlot<?> first = new AbstractLinkedProcessorSlot<Object>() {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
        throws Throwable {
        // 默认逻辑是向后传递
        super.fireEntry(context, resourceWrapper, t, count, prioritized, args);
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        // 默认逻辑是向后传递
        super.fireExit(context, resourceWrapper, count, args);
    }

};

fireEntry()就是调用下一个Slot的entry()方法,所以Slot主要关注entry()方法即可。

com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot#fireEntry

public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
    throws Throwable {
    if (next != null) {
        next.transformEntry(context, resourceWrapper, obj, count, prioritized, args);
    }
}

@SuppressWarnings("unchecked")
void transformEntry(Context context, ResourceWrapper resourceWrapper, Object o, int count, boolean prioritized, Object... args)
    throws Throwable {
    T t = (T)o;
    entry(context, resourceWrapper, t, count, prioritized, args);
}

NodeSelectorSlot

NodeSelectorSlot主要负责收集调用链路的路径,后续可用于流控规则的调用链路限流。

com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot#entry

public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
    throws Throwable {

    // 这里是根据context name获取DefaultNode,而不是根据resource name获取
    DefaultNode node = map.get(context.getName());
    if (node == null) {
        synchronized (this) {
            node = map.get(context.getName());
            if (node == null) {
                // 根据资源创建DefaultNode
                node = new DefaultNode(resourceWrapper, null);

                // 将DefaultNode放入map中,采用CopyOnWrite保证线程安全
                // 相当于 map.put(context.getName(), node);
                HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
                cacheMap.putAll(map);
                cacheMap.put(context.getName(), node);
                map = cacheMap;

                // 构建调用链树
                // Build invocation tree
                ((DefaultNode) context.getLastNode()).addChild(node);
            }

        }
    }

    // 设置当前Node
    context.setCurNode(node);
    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

ClusterBuilderSlot

ClusterBuilderSlot主要负责存储资源的统计信息,后续将用于流控规则。

com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot#entry

public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                  boolean prioritized, Object... args)
    throws Throwable {
    if (clusterNode == null) {
        synchronized (lock) {
            if (clusterNode == null) {
                // 根据资源创建ClusterNode
                // Create the cluster node.
                clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());

                // 将ClusterNode放入clusterNodeMap中,采用CopyOnWrite保证线程安全
                // 相当于 clusterNodeMap.put(node.getId(), node);
                HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
                newMap.putAll(clusterNodeMap);
                newMap.put(node.getId(), clusterNode);
                // 注意clusterNodeMap是一个静态变量,整个实例共享
                clusterNodeMap = newMap;
            }
        }
    }
    node.setClusterNode(clusterNode);

    /*
     * if context origin is set, we should get or create a new {@link Node} of
     * the specific origin.
     */
    if (!"".equals(context.getOrigin())) {
        // origin从哪来的?
        // 在外部手动调用InternalContextUtil.internalEnter创建context,并指定origin
        // 创建OriginNode
        Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
        context.getCurEntry().setOriginNode(originNode);
    }

    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

StatisticSlot

StatisticSlot主要负责统计数据,数据存储在之前的Slot中创建的DefaultNode和ClusterNode中。

com.alibaba.csp.sentinel.slots.statistic.StatisticSlot#entry

public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                  boolean prioritized, Object... args) throws Throwable {
    try {
        // Do some checking.
        fireEntry(context, resourceWrapper, node, count, prioritized, args);

        // 目标方法调用完成之后进行统计
        // Request passed, add thread count and pass count.
        // DefaultNode和ClusterNode 线程数+1
        node.increaseThreadNum();
        // DefaultNode和ClusterNode QPS+count
        node.addPassRequest(count);

        if (context.getCurEntry().getOriginNode() != null) {
            // Add count for origin node.
            // OriginNode  线程数+1
            // 为什么不是context.getOriginNode()
            // context.getCurEntry().getOriginNode() == context.getOriginNode()
            context.getCurEntry().getOriginNode().increaseThreadNum();
            // OriginNode QPS+count
            context.getCurEntry().getOriginNode().addPassRequest(count);
        }

        // TODO IN 用来干啥
        if (resourceWrapper.getEntryType() == EntryType.IN) {
            // Add count for global inbound entry node for global statistics.
            // 记录整个系统的线程数和QPS,可用于系统规则
            Constants.ENTRY_NODE.increaseThreadNum();
            Constants.ENTRY_NODE.addPassRequest(count);
        }

        // 扩展点,回调
        // Handle pass event with registered entry callback handlers.
        for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
            handler.onPass(context, resourceWrapper, node, count, args);
        }
    } catch (PriorityWaitException ex) {
        ... ...
    } catch (BlockException e) {
        // Blocked, set block exception to current entry.
        // 设置限流异常,后面的slot将会使用
        context.getCurEntry().setBlockError(e);

        // DefaultNode和ClusterNode blockQPS +1
        // Add block count.
        node.increaseBlockQps(count);
        if (context.getCurEntry().getOriginNode() != null) {
            // OriginNode blockQPS +1
            context.getCurEntry().getOriginNode().increaseBlockQps(count);
        }

        if (resourceWrapper.getEntryType() == EntryType.IN) {
            // Add count for global inbound entry node for global statistics.
            // 整个系统的blockQPS +1
            Constants.ENTRY_NODE.increaseBlockQps(count);
        }

        // 扩展点,回调
        // Handle block event with registered entry callback handlers.
        for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
            handler.onBlocked(e, context, resourceWrapper, node, count, args);
        }

        throw e;
    } catch (Throwable e) {
        // Unexpected internal error, set error to current entry.
        // 设置限流异常,后面的slot将会使用
        context.getCurEntry().setError(e);

        throw e;
    }
}

AuthoritySlot、SystemSlot、ParamFlowSlot、DegradeSlot前面的文章已分析,这里不再赘述。

FlowSlot

FlowSlot#entry()会在目标方法执行之前进行流控规则的校验。

com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#entry

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);
}

void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
    throws BlockException {
    // 校验
    checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
}

一个资源可以配置多个流控规则,所以需要遍历所有的规则逐一校验,校验不通过就会抛出FlowException。

com.alibaba.csp.sentinel.slots.block.flow.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());
    if (rules != null) {
        for (FlowRule rule : rules) {
            // 遍历所有的流控规则,逐一校验
            if (!canPassCheck(rule, context, node, count, prioritized)) {
                // 校验不通过,抛出FlowException
                throw new FlowException(rule.getLimitApp(), rule);
            }
        }
    }
}

public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node,
                                         int acquireCount) {
    return canPassCheck(rule, context, node, acquireCount, false);
}

public boolean canPassCheck(/*@NonNull*/ 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);
}

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;
    }

    /** 根据流控效果来校验
     * DefaultController为直接拒绝
     * RateLimiterController为排队等待
     * WarmUpController为预热
     *
     * @see RateLimiterController#canPass(com.alibaba.csp.sentinel.node.Node, int, boolean)
     * @see WarmUpController#canPass(com.alibaba.csp.sentinel.node.Node, int, boolean)
     * @see DefaultController#canPass(com.alibaba.csp.sentinel.node.Node, int, boolean)
     */
    return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
}

selectNodeByRequesterAndStrategy()主要是根据不同的流控模式选择不同的Node,这里总结下:

  • 根据针对来源orgin限流,使用OriginNode
  • 根据资源名直接限流,使用ClusterNode
  • 根据关联关系限流,根据关联的资源名称从ClusterBuilderSlot中查找ClusterNode
  • 根据调用链路限流,使用DefaultNode

com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker#selectNodeByRequesterAndStrategy

static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) {
    // The limit app should not be empty.
    String limitApp = rule.getLimitApp();
    int strategy = rule.getStrategy();
    String origin = context.getOrigin();

    if (limitApp.equals(origin) && filterOrigin(origin)) {
        // limitApp和origin相同,表示要根据来源限流
        if (strategy == RuleConstant.STRATEGY_DIRECT) {
            // 流控模式为直接
            // Matches limit origin, return origin statistic node.
            // 在中通过context.getCurEntry().setOriginNode(originNode)设置的
            /**
             * 相当于context.getCurEntry().getOriginNode();
             * @see ClusterBuilderSlot#entry(com.alibaba.csp.sentinel.context.Context, com.alibaba.csp.sentinel.slotchain.ResourceWrapper, com.alibaba.csp.sentinel.node.DefaultNode, int, boolean, java.lang.Object...)
             */
            return context.getOriginNode();
        }

        // 到这里origin将失效,与针对来源设置为default逻辑一致
        return selectReferenceNode(rule, context, node);
    } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
        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())) {
        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) {
        // 流控模式为关联关系,根据refResource找到对应的ClusterBuilderSlot
        return ClusterBuilderSlot.getClusterNode(refResource);
    }

    // 流控模式为调用链路
    if (strategy == RuleConstant.STRATEGY_CHAIN) {
        if (!refResource.equals(context.getName())) {
            // 如果refResource不等于context.getName(),返回null,不进行限流
            // 如果web-context-unify=false,contextName为sentinel_spring_web_context
            // refResource肯定不等于context.getName()[sentinel_spring_web_context],所以不会限流
            // 如果web-context-unify=true,contextName为url
            return null;
        }
        // web-context-unify=true才会到这里
        return node;
    }
    // No node.
    return null;
}

各种流控效果的实现类:

DefaultController是处理流控效果为直接拒绝,也就是直接抛出异常,其他流控效果涉及到具体的算法,故另开文章分析。
com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController#canPass(com.alibaba.csp.sentinel.node.Node, int, boolean)

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) {
            // prioritized默认为false,不会进入
            ... ...
            }
        }
        // 请求数大于阈值直接返回false,抛出FlowException
        return false;
    }
    return true;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/546877.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

跨境seo引流的13种方法

跨境SEO引流是一种通过搜索引擎优化来吸引国际目标受众并增加网站流量的策略。以下是一些跨境SEO引流的关键步骤和技巧&#xff1a; 目标受众研究&#xff1a;了解你的目标市场和受众群体。了解他们的需求、喜好、购买习惯以及使用的搜索引擎和关键词。这将帮助你确定你的跨境S…

chatgpt赋能Python-python3怎么合并列表

Python3&#xff1a;合并列表的不同方法 如果你正在使用Python 3&#xff0c;那么你很可能会面对合并列表的问题。合并列表&#xff08;也称为连接列表或串联列表&#xff09;是将两个或多个列表组合成一个列表的过程&#xff0c;这是在编程中很常见的任务。在这篇文章里&…

Python并发编程:异步编程和多线程技术的应用和效率优化

第一章&#xff1a;介绍 在当今的软件开发领域&#xff0c;高效的并发编程对于处理大规模数据和提升系统性能至关重要。Python作为一种简洁、易读且功能强大的编程语言&#xff0c;提供了多种并发编程的技术和工具。本文将深入探讨Python中的异步编程和多线程技术&#xff0c;…

chatgpt赋能Python-python3如何画图

Python3如何画图&#xff1f; Python是一种高级编程语言&#xff0c;它有着多种用途&#xff0c;包括数据分析和可视化。Python3是Python的最新版本&#xff0c;它具有更好的性能和易用性。在这篇文章中&#xff0c;我们将介绍如何使用Python3来画图&#xff0c;并探讨其优势和…

KingbaseES 逻辑读与物理读

oracle数据库中逻辑读&#xff0c;物理读 数据访问方式&#xff1a;数据库少不了和操作系统进行数据交互&#xff0c;表数据最好的方式是从数据库共享池中访问到&#xff0c;避免发生磁盘IO&#xff0c;当然如果共享池中没有访问到数据就难免发生磁盘IO。 物理读&#xff1a;从…

第三篇、Arduino uno、nano、2560用oled0.96寸屏幕显示dht11温湿度传感器的温度和湿度信息——结果导向

0、结果 说明&#xff1a;先来看看拍摄的显示结果&#xff0c;如果是你想要的&#xff0c;可以接着往下看。 1、外观 说明&#xff1a;本次使用的oled是0.96寸的&#xff0c;别的规格的屏幕不一定适用本教程&#xff0c;一般而言有显示白色、蓝色和蓝黄一起显示的&#xff0…

【小沐学Web】Node实现Web图表功能(ECharts.js,React)

&#x1f388;&#x1f388;&#x1f388;Python实现Web图表功能系列&#xff1a;&#x1f388;&#x1f388;&#x1f388;1&#x1f388;【Web开发】Python实现Web图表功能&#xff08;D-Tale入门&#xff09;&#x1f388;2&#x1f388;【Web开发】Python实现Web图表功能&a…

Fragment 要你何用?2.0版本

前言 在之前的文章里有分析过Activity、Fragment、View之间的关联&#xff0c;也简单分析了Fragment的原理。 本篇将对Fragment被高频使用的场景以及一些坑点作分析&#xff0c;通过本篇文章&#xff0c;你将了解到&#xff1a; 老生常谈&#xff1a;为什么需要Fragment?Frag…

Java 创建一个大文件

有时候&#xff0c;我们在对文件进行测试的时候&#xff0c;可能需要创建一个临时的大文件。 那么问题来了&#xff0c;在 Java 中如何创建大文件呢&#xff1f; 问题和解决 有些人想到的办法就是定义一个随机的字符串&#xff0c;然后重复很多次&#xff0c;然后将这个字符…

第一篇:强化学习基本原理通俗介绍

你好&#xff0c;我是zhenguo&#xff08;郭震&#xff09; 今天强化学习第一篇&#xff1a;白话介绍强化学习的基本原理 强化学习是一种机器学习方法&#xff0c;旨在让智能体&#xff08;agent&#xff09;通过与环境的交互学习如何做出最优的行动选择以获得最大的累积奖励。…

Rust每日一练(Leetday0004) 正则表达、盛水容器、转罗马数字

目录 10. 正则表达式匹配 Regular Expression Matching &#x1f31f;&#x1f31f;&#x1f31f; 11. 盛最多水的容器 Container with most water &#x1f31f;&#x1f31f; 12. 整数转罗马数字 Integer to Roman &#x1f31f;&#x1f31f; &#x1f31f; 每日一练…

new和delete用法详解

本篇文章对C中的new和delete进行详解。在讲解new和delete时&#xff0c;我们会对比C语言中的malloc和free&#xff0c;看看两者的区别和相似之点。希望本篇文章会对你有所帮助。 文章目录 一、什么是new和delete 二、new和delete的用法 2、1 new和delete操作内置类型 2、2 new和…

中青宝两大议案被否!散户又“赢了”?

21.93万股&#xff0c;就能决定股东大会上的议案成败——离奇的一幕在中青宝上演。 5月18日&#xff0c;中青宝召开2022年度股东大会。会上&#xff0c;《关于2023年度日常关联交易预计的议案》《关于非独立董事2023年度薪酬方案的议案》两项议案被否。 此次股东大会上&#x…

linux设置静态ip与windows互相ping通、设置静态ip之后不能联网和网络服务重启失败的问题

1.虚拟机linux设置静态ip与windows互相ping通及设置静态ip之后不能联网问题一站式解决&#xff1a; 转载&#xff1a;https://www.codenong.com/cs105332412/ 2.遇到网络服务重启失败的问题 按照提示查看网络服务的状态 看到这篇博文https://www.cyberithub.com/failed-to-s…

Ae 效果详解:Keylight(1.2)

Ae菜单&#xff1a;效果/Keying/Keylight(1.2) Effects/Keying/Keylight(1.2) Keylight 是一款工业级的蓝幕或绿幕键控器&#xff0c;核心算法由 Computer Film 公司开发&#xff0c;并由 The Foundry 公司进一步开发移植到 Ae。 Keylight 在制作专业品质的抠像效果方面表现出色…

第11章_数据处理之增删改

第11章_数据处理之增删改 1. 插入数据 1.1 实际问题 解决方式&#xff1a;使用 INSERT 语句向表中插入数据。 1.2 方式1&#xff1a;VALUES的方式添加 使用这种语法一次只能向表中插入一条数据。 情况1&#xff1a;为表的所有字段按默认顺序插入数据 INSERT INTO 表名 VAL…

Python 学习 2022.08.28 周日

文章目录 一、 概述1.1&#xff09; 之前写的文章&#xff1a;1.2) 基础点1.3) 配置1.4) Python2 和 Python3 的区别1.5&#xff09; 相关问题跟踪解决1.6) 其他 一、 概述 1.1&#xff09; 之前写的文章&#xff1a; 【Python大系】Python快速教程《Python 数据库 GUI CGI编…

clion开发stm32之flash驱动(f4系列)

前言 使用的开发工具(clionmsys2openocd)使用的开发版芯片stm32f407vet6参考手册为stm32f4中文参考文档 查看中文手册 ## 驱动代码 头文件(bsp_flash.h) #ifndef STM32F103VET6_PROJECT_BSP_FLASH_H #define STM32F103VET6_PROJECT_BSP_FLASH_H #include "sys.h"…

华硕幻X 2023 Windows11原厂预装系统 工厂恢复安装带ASUSRecevory一键还原

华硕幻X 2023 Windows11原厂预装系统 工厂模式恢复安装带ASUSRecevory一键还原 文件地址&#xff1a;https://pan.baidu.com/s/1snKOsH3OMl3GZLqeAf-GLA?pwd8888 华硕工厂恢复系统 &#xff0c;安装结束后带隐藏分区以及机器所有驱动软件 需准备一个16G左右空u盘进行恢复 …

chatgpt赋能Python-python3怎么用

Python3入门指南&#xff1a;从基础到进阶 Python是一款简单而强大的编程语言&#xff0c;具有易读性、易学性和高生产性的特点。它广泛应用于数据分析、机器学习、Web开发、自动化测试等领域。Python的第三个版本&#xff08;Python3&#xff09;相对于第二个版本&#xff08…