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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端026_菜单模块_新增功能

菜单模块_新增功能 1、需求分析2、新增组件实现3、列表引用新增组件4、关闭弹出窗口5、校验表单数据6、提交表单数据6.1、Mock 添加新增模拟接口6.2、Api 调用接口6.3、测试新增功能1、需求分析 菜单管理中有两处有 新增 按钮: 条件区域的是新增一级菜单,传递的参数是0。列表…

Compose 二三事:绘制原理

setContent做了什么 我们基于一个最简单的例子进行分析 class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Text(text "Hello World!")}} }这里setContent做了什么…

NetApp FAS 混合闪存阵列协助您建构简单易用、聪明智慧、值得信赖的储存基础架构

NetApp FAS 混合闪存阵列 主要优势 1、简单易用&#xff1a;节省您宝贵的时间、金钱和人力 •几分钟内完成储存资源配置。 •以获证实的效率技术降低成本。 •可在单一系统上管理档案与区块资料。 2、聪明智慧&#xff1a;灵活因应瞬息万变的业务需求 •以不中断营运的方式扩…

java(springboot+ssm)/python/php/nodejs/基于vue的景区门票预约管理系统

后端&#xff1a;java(springbootssm)/python/php/nodejs/ 开发运行&#xff1a;微信开发者/hbuilderx 后端:idea/eclipse/vscode/pycharm 模块划分&#xff1a;公告类型、公告信息、用户信息、用户咨询、地区信息、景区信息、景区开放、景区预约、统计信息 本技术是Java平台的…

企企通“码上顺”清洗工具 | 让数据更有价值,让业务更出色

数据清理工作是企业数据管理、数据治理中的最基础的工作之一&#xff0c;不仅是一项苦活、累活&#xff0c;也是一个既考验业务又检验技术的活。 物料主数据作为企业核心的数据资产&#xff0c;在智慧供应链、业财一体化等数字化建设中发挥着重要作用。在当今高速发展的商业环…

2023新版Spring6全新讲解-HelloSpring入门案例

Spring的入门案例 Spring6.0要求的JDK最低版本是17 我们在本课程中使用的版本是5.x版本。这个Spring5的JDK的最低要求是8 一、环境要求 JDK&#xff1a;8 Maven&#xff1a;3.6 Spring:5.3.27 开发工具&#xff1a;IDEA 2021.1.1 二、项目创建 1. 构建项目 在idea中&…

GEE:GEDI数据提取值到矢量区域和点

作者:CSDN @ _养乐多_ 本文将介绍GEDI数据集从GEE上下载到本地,并将每一个激光点的值提取为一个矢量区域,并提取值到矢量区域的方法。 文章目录 一、GEDI数据下载二、GEDI数据栅格矢量化三、提取值到区域四、提取栅格值到点五、空间插值一、GEDI数据下载 GEDI数据下载链接:…

opencv相机标定

当你把摄像机放在一个特定的位置&#xff0c;在它的后面放一个目标图像&#xff0c;或者是把摄像机放到某个物体上&#xff0c;摄像机周围的物体是什么形状&#xff0c;你需要知道这些信息。 当你在计算机上处理图像时&#xff0c;会使用以下三个参数&#xff1a; 1.像素坐标&a…

【我的创作纪念日】—— 纪念四年的坚持

这是一篇和技术无关的博客&#xff0c;但对我而言&#xff0c;它承载了不菲的价值 普通且宁静的一天&#xff0c;被一条消息戳中&#xff0c;于是&#xff0c;写一篇分享帖&#xff0c;纪念我这 1460 天的坚持初衷&#xff1a; 前言&#xff1a;对过去的回顾 4 年前的我&#…

2098-DSD-020X 具有集成的DeviceNet通信接口

描述:2098-DSD-020X-DN是艾伦-布拉德利Ultra 3000运动控制系列的一部分。该产品是一种数字伺服驱动器&#xff0c;可在120VAC / 240 VAC、单相、50-60 Hz的输入电源电压和18安培的输入电流下运行。该伺服驱动器提供120 / 240 VAC的输出电压、3相、0-400 Hz的可编程频率范围、10…

深度学习训练营之Densenet网络

深度学习训练营 原文链接环境介绍前言设计理念网络结构实验结果和讨论pytorch实现DenseNet附录 原文链接 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章&#xff1a;365天深度学习训练营-第J3周&#xff1a;Densenet网络学习&…

【数据分享】1901-2021年1km分辨率逐月平均气温栅格数据(全国/分省/免费获取)

气温数据是我们最常用的气象指标之一&#xff0c;之前我们给大家分享过1950-2022年0.1 x 0.1精度的逐月平均气温栅格数据和逐年平均气温栅格数据&#xff08;均可查询之前的文章获悉详情&#xff09;&#xff01; 本次我们分享的是精度更高的气温栅格数据——1901-2021年1km分…

【C++】类和对象(3)

文章目录 一、初始化列表二、explicit关键字三、static成员四、友元4.1 友元函数4.2 友元类 五、内部类六、匿名对象七、编译器的优化 一、初始化列表 首先我们先回顾一下构造函数&#xff0c;对象的初始化由构造函数来完成&#xff0c;我们可以在构造函数的函数体内对对象的成…

asp.net就业满意度问调查系统

本系统主要有会员&#xff08;调查者&#xff09;和管理员&#xff0c;他们具体的功能如下&#xff1a; 会员功能&#xff1a;注册&#xff0c;登录&#xff0c;修改个人信息&#xff0c;调查&#xff0c;查看调查结果及影响&#xff0c;留言,首先是会员注册&#xff0c;注册后…

广域通信网 - HDLC 高级数据链路控制协议

文章目录 1 概述2 HDLC2.1 帧类型2.2 帧结构 3 扩展3.1 网工软考真题 1 概述 #mermaid-svg-JEuFH1qP4tY5jI5p {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JEuFH1qP4tY5jI5p .error-icon{fill:#552222;}#mermaid-…

python如何连接mysql数据库

python链接mysql数据库要用到pymysql模块中的connect &#xff0c;connect函数是pymysql模块中 用于连接MySQL数据库的一个函数。 所以连接mysql之前需要先导入pymysql模块。 第一步&#xff0c;mysql模块下载 方法1&#xff08;使用pip命令安装&#xff09;&#xff1a; 因…

MySQL数据库基础4-内置函数

文章目录 日期函数字符串函数数学函数其他函数 日期函数 函数名称描述current date()当前日期current time()当前时间current timestamp()当前时间戳date(datetime)返回datetime参数的日期部分date add(date, interval d_value type)在date中添加日期或时间&#xff0c;interv…

调用百度API自动生成春联

目录 1、作者介绍2、百度智能春联介绍录2.1 功能介绍2.2 技术特色 3、智能春联API接口介绍3.1 请求参数3.2 返回参数 4. 操作流程5. 代码实现 1、作者介绍 范宇帅&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生 研究方向&#xff1a;多机器人协…

Windows安装Ubuntu双系统

Windows安装Ubuntu双系统 1.下载Ubuntu 16.04&#xff0c;地址https://releases.ubuntu.com/16.04/ 2.下载Rufus&#xff0c;地址https://rufus.ie/zh/ 3.准备U盘&#xff0c;烧录系统 4.磁盘分区 5.重启&#xff0c;按住shift键 本人电脑是联想小新 Windows11系统&#xff0…

Elasticsearch环境搭建(Windows)

一、介绍 布式、RESTful 风格的搜索和分析。 Elasticsearch 是位于 Elastic Stack 核心的分布式搜索和分析引擎。Logstash 和 Beats 有助于收集、聚合和丰富您的数据并将其存储在 Elasticsearch 中。Kibana 使您能够以交互方式探索、可视化和分享对数据的见解&#xff0c;并管…