sentinel源码分析-05核心链路调用

YOLOv8/v9工业零件缺陷检测实战指南:从原理到产线部署 摘要:本文系统介绍基于YOLOv8/v9的工业零件缺陷检测技术,从工业质检背景与需求出发,详解缺陷检测原理、算法构建与模型训练,提供从数据集准备到移动端部署的全流程实操方案。包含多尺度检测、纹理分析等高级技术,适配工业相机的环境优化策略,以及完整质检系统代码实现。适合工业视觉领域新手与进阶开发者,助力快速构建高鲁棒性的缺陷检测系统。 阅读详情

核心链路调用

Entry e = new CtEntry(resourceWrapper, chain, context);
try {
    //开启链路调用
    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;

资源调用会创建一个CtEntry,然后开始执行上面创建的执行链

NodeSelectorSlot
private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
        throws Throwable {
        //一个context+resource确定一个DefaultNode
        DefaultNode node = map.get(context.getName());
        //双重检查
        if (node == null) {
            synchronized (this) {
                //运行上下对应一个DefaultNode
                node = map.get(context.getName());
                if (node == null) {
                    node = new DefaultNode(resourceWrapper, null);
                    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);
            }
        }

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

这个slot主要是为了收集资源路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级。首先判断缓存map中是否已经生成了DefaultNode,如果没有则进入双重检查逻辑,创建DefaultNode并设置资源,一个context+resource确定一个DefaultNode,构建请求的资源路径,构建树状结构,并且设置context中当前调用的node。这个类上有详细的注解解释我们要仔细看一下

ContextUtil.enter("entrance1", "appA");
Entry nodeA = SphU.entry("nodeA");
if (nodeA != null) {
   nodeA.exit();
}
ContextUtil.exit();

上述代码通过 ContextUtil.enter() 创建了一个名为 entrance1 的上下文,同时指定调用发起者为 appA;接着通过 SphU.entry()判断请求是否可以正常通过,如果不能顺利通过发生限流降级则会抛出 BlockException。

以上代码将在内存中生成以下结构:

				machine-root
 *                  /
 *                 /
 *           EntranceNode1
 *               /
 *              /
 *        DefaultNode(nodeA)- - - - - -> ClusterNode(nodeA);

每个 DefaultNode 由资源 ID 和输入名称来标识,同一个资源在不同的context中会有不同的DefaultNode,比如

ContextUtil.enter("entrance1", "appA");
Entry nodeA = SphU.entry("nodeA");
 if (nodeA != null) {
     nodeA.exit();
 }
ContextUtil.exit();

ContextUtil.enter("entrance2", "appA");
nodeA = SphU.entry("nodeA");
if (nodeA != null) {
  nodeA.exit();
}
ContextUtil.exit();

以上代码将在内存中生成以下结构:

machine-root
 *                  /         \
 *                 /           \
 *         EntranceNode1   EntranceNode2
 *               /               \
 *              /                 \
 *      DefaultNode(nodeA)   DefaultNode(nodeA)
 *             |                    |
 *             +- - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);

可以看到同一个资源在不同context中会有不同的DefaultNode,但是全局只有有一个ClusterNode

ClusterBuilderSlot
private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>();

    private static final Object lock = new Object();

    private volatile ClusterNode clusterNode = null;

    @Override
    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) {
                    // Create the cluster node.
                    //创建clusterNode,用于统计全局请求指标
                    clusterNode = new ClusterNode();
                   	//这里为什么不使用ConcurrentHashMap
                    HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
                    newMap.putAll(clusterNodeMap);
                    newMap.put(node.getId(), clusterNode);

                    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())) {
            Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
            context.getCurEntry().setOriginNode(originNode);
        }

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

源码中可以看到ClusterNode和ResourceWrapper绑定,在这里构建了资源的clusterNode和请求源originNode,这里有个疑问为什么不使用ConcurrentHashMap,而是使用普通HashMap通过加锁的形式来设置ClusterNode呢?

这里可能是为了高并发考虑,使用copyOnWrite操作,clusterNodeMap是使用static volatile修饰的,如果直接put其他线程无法感知到map内容的变化,通过copyOnWrite操作重新赋值,会使clusterNodeMap地址发生改变,其他线程会感知到变化,保证数据一致性,同时起到读写分离的效果,提高读写并发,同时创建的临时map进行了初始容量的赋值,不会因为扩容导致性能波动。

LogSlot

public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode obj, int count, boolean prioritized, Object… args)
throws Throwable {
try {
fireEntry(context, resourceWrapper, obj, count, prioritized, args);
} catch (BlockException e) {
EagleEyeLogUtil.log(resourceWrapper.getName(), e.getClass().getSimpleName(), e.getRuleLimitApp(),
context.getOrigin(), count);
throw e;
} catch (Throwable e) {
RecordLog.warn(“Unexpected entry exception”, e);
}

}
这里可以看到该slot没做任何逻辑处理,只有在抛出BlockException时或其他异常时打印日志输出
StatisticSlot
@Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        try {
            // Do some checking.
            //先执行后面slot,如果校验通过在执行统计
            fireEntry(context, resourceWrapper, node, count, prioritized, args);

            // Request passed, add thread count and pass count.
            //增加请求线程数
            node.increaseThreadNum();
            //增加统计请求数
            node.addPassRequest(count);
            //如果存在源节点,则增加源节点的统计信息
            if (context.getCurEntry().getOriginNode() != null) {
                // Add count for origin node.
                //计算请求源的线程数和请求通过的请求
                context.getCurEntry().getOriginNode().increaseThreadNum();
                context.getCurEntry().getOriginNode().addPassRequest(count);
            }
            //增加全局统计
            if (resourceWrapper.getType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                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) {
            node.increaseThreadNum();
            if (context.getCurEntry().getOriginNode() != null) {
                // Add count for origin node.
                context.getCurEntry().getOriginNode().increaseThreadNum();
            }

            if (resourceWrapper.getType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseThreadNum();
            }
            // Handle pass event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);
            }
        } catch (BlockException e) {
            // Blocked, set block exception to current entry.
            //设置error信息
            context.getCurEntry().setError(e);

            // Add block count.
            //计算被阻塞的请求数量
            node.increaseBlockQps(count);
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().increaseBlockQps(count);
            }

            if (resourceWrapper.getType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                //计算全局请求阻塞的数量
                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 error, set error to current entry.
            context.getCurEntry().setError(e);

            // This should not happen.
            //计算发生异常调用
            node.increaseExceptionQps(count);
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().increaseExceptionQps(count);
            }
            //计算全局发生异常调用
            if (resourceWrapper.getType() == EntryType.IN) {
                Constants.ENTRY_NODE.increaseExceptionQps(count);
            }
            throw e;
        }
    }

这里可以看到该slot主要完成调用数据的实时统计

SystemSlot
@Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        //全局的指标统计
        SystemRuleManager.checkSystem(resourceWrapper);
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockException {
        // Ensure the checking switch is on.
        if (!checkSystemStatus.get()) {
            return;
        }

        // for inbound traffic only
        //只统计进入的请求
        if (resourceWrapper.getType() != EntryType.IN) {
            return;
        }

        // total qps 全局QPS统计
        double currentQps = Constants.ENTRY_NODE == null ? 0.0 : Constants.ENTRY_NODE.successQps();
        if (currentQps > qps) {
            throw new SystemBlockException(resourceWrapper.getName(), "qps");
        }

        // total thread 全局线程统计
        int currentThread = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.curThreadNum();
        if (currentThread > maxThread) {
            throw new SystemBlockException(resourceWrapper.getName(), "thread");
        }
        //全局平均请求RT
        double rt = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.avgRt();
        if (rt > maxRt) {
            throw new SystemBlockException(resourceWrapper.getName(), "rt");
        }

        // load. BBR algorithm.
        //计算负载
        if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {
            if (!checkBbr(currentThread)) {
                throw new SystemBlockException(resourceWrapper.getName(), "load");
            }
        }

        // cpu usage
        //计算cpu使用
        if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {
            if (!checkBbr(currentThread)) {
                throw new SystemBlockException(resourceWrapper.getName(), "cpu");
            }
        }
    }
AuthoritySlot
@Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
        throws Throwable {
        checkBlackWhiteAuthority(resourceWrapper, context);
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
        //获取配置的权限控制规则
        Map<String, Set<AuthorityRule>> authorityRules = AuthorityRuleManager.getAuthorityRules();
        //不存在则直接跳过
        if (authorityRules == null) {
            return;
        }
        //获取针对请求资源的授权规则
        Set<AuthorityRule> rules = authorityRules.get(resource.getName());
        if (rules == null) {
            return;

根据配置的黑白名单和调用来源信息,来做黑白名单控制

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

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)) {
                    throw new FlowException(rule.getLimitApp(), rule);
                }
            }
        }
    }

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

        return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
    }

这里通过资源名称获取dashboard配置的流控规则,一次判断规则是否可以通过,根据配置的规则是找到对应的node

static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) {
        // The limit app should not be empty.
    	//是否针对调用来源,默认为default就是不区分
        String limitApp = rule.getLimitApp();
        int strategy = rule.getStrategy();
        String origin = context.getOrigin();
        //针对的调用来源和请求源相同
        if (limitApp.equals(origin) && filterOrigin(origin)) {
            if (strategy == RuleConstant.STRATEGY_DIRECT) {//快速失败
                // Matches limit origin, return origin statistic node.
                //返回请求源node
                return context.getOriginNode();
            }
            //查询关联的其他请求
            return selectReferenceNode(rule, context, node);
            //如果limitApp为default,及针对所有请求
        } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
            if (strategy == RuleConstant.STRATEGY_DIRECT) {//快速失败
                // Return the cluster node.
                //返回clusterNode
                return node.getClusterNode();
            }
            //查询关联的其他请求的node
            return selectReferenceNode(rule, context, node);
            //如果是other及配了当前几个资源之外的其他资源使用的规则
        } else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
            && FlowRuleManager.isOtherOrigin(origin, rule.getResource())) {
            if (strategy == RuleConstant.STRATEGY_DIRECT) {
                return context.getOriginNode();
            }
            //查询关联的其他请求的node
            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) {
            return ClusterBuilderSlot.getClusterNode(refResource);
        }
        //请求链
        if (strategy == RuleConstant.STRATEGY_CHAIN) {
            if (!refResource.equals(context.getName())) {
                return null;
            }
            return node;
        }
        // No node.
        return null;
    }

我们先来看看FlowRule中的重要属性

private int grade = RuleConstant.FLOW_GRADE_QPS;//限流类型,线程总数或QPS
private double count;//流量控制阈值计数
private int strategy = RuleConstant.STRATEGY_DIRECT;//限制本资源
private String refResource;//使用相关资源或上下文引用流控制中的资源。引用资源
private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;//流控策略,0:快速失败 1.预热 2.匀速通过 3.预热+匀速通过
private int warmUpPeriodSec = 10;//预热时间
private int maxQueueingTimeMs = 500;//最大排队时间
private boolean clusterMode;//是否是集群限流模式
private ClusterFlowConfig clusterConfig;//集群限流配置
private TrafficShapingController controller;//流量整形(节流)控制器

这里根据配置的规则针对的来源分为3类

  • 设置了针对的来源,则会判断来源和设置的针对来源是否相同,如果相同而且来源不是default或者other则说明命中,再判断设置的限流策略如果是直接则直接返回请求源originNode,如果是请求链路则返回DefaultNode,如果是关联,则根据规则中设置的关联资源,找到对应关联资源的clusterNode然后返回
  • 如果针对来源没有设置及为default,则是针对当前资源进行流控,再判断设置的限流策略如果是直接则直接返回资源ClusterNode,如果是请求链路则返回DefaultNode,如果是关联,则根据规则中设置的关联资源,找到对应关联资源的clusterNode然后返回
  • 如果是other及配了当前几个资源之外的其他资源使用的规则,再判断设置的限流策略如果是直接则直接返回当前资源之外的请求源originNode,如果是请求链路则返回DefaultNode,如果是关联,则根据规则中设置的关联资源,找到对应关联资源的clusterNode然后返回

根据返回node进行数据统计是否可以正常通过,这里我们认为是DefaultController快速失败

@Override
    public boolean canPass(Node node, int acquireCount, boolean prioritized) {
        //已经统计的数
        int curCount = avgUsedTokens(node);
        //已经统计的数+请求数 > 限流数量,则返回false代表限流
        if (curCount + acquireCount > count) {
            //是优先权而且是根据QPS限流
            if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {
                long currentTime;
                long waitInMs;
                currentTime = TimeUtil.currentTimeMillis();
                //尝试抢用后续时间窗的许可值,返回该时间窗口所剩余的时间,如果获取失败,
                //则返回 OccupyTimeoutProperty.getOccupyTimeout()
                waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);
                //等待时间不会超过一个时间窗
                if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {
                    //将请求数添加到借用的borrowArray一个未来时间窗
                    node.addWaitingRequest(currentTime + waitInMs, acquireCount);
                    node.addOccupiedPass(acquireCount);
                    //等待一段时间
                    sleep(waitInMs);

                    // PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}.
                    //表示等待一定时间后可以通过
                    throw new PriorityWaitException(waitInMs);
                }
            }
            return false;
        }
        return true;
    }

public long tryOccupyNext(long currentTime, int acquireCount, double threshold) {
        //可以通过的最大请求数
        double maxCount = threshold * IntervalProperty.INTERVAL / 1000;
        //获取已经借用的请求数
        long currentBorrow = rollingCounterInSecond.waiting();
        //大于最大请求数量了则返回默认值500ms
        if (currentBorrow >= maxCount) {
            return OccupyTimeoutProperty.getOccupyTimeout();
        }
        //窗口长度,默认值500
        int windowLength = IntervalProperty.INTERVAL / SampleCountProperty.SAMPLE_COUNT;
        //earliestTime为currentTime往前推了一个周期
        long earliestTime = currentTime - currentTime % windowLength + windowLength - IntervalProperty.INTERVAL;

        int idx = 0;
        /*
         * Note: here {@code currentPass} may be less than it really is NOW, because time difference
         * since call rollingCounterInSecond.pass(). So in high concurrency, the following code may
         * lead more tokens be borrowed.
         */
        long currentPass = rollingCounterInSecond.pass();
        while (earliestTime < currentTime) {
            //当前窗口剩余的时间
            long waitInMs = idx * windowLength + windowLength - currentTime % windowLength;
            //设置窗口时间不能大于一个时间窗
            if (waitInMs >= OccupyTimeoutProperty.getOccupyTimeout()) {
                break;
            }
            //获取前一个时间窗通过请求数
            long windowPass = rollingCounterInSecond.getWindowPass(earliestTime);
            //当前pass数量+借用的数量+当前需要获取的数量-1s前通过的数量<=最大通过数量
            //也就是近两个时间窗口通过的请求和不能是maxCount的2倍
            if (currentPass + currentBorrow + acquireCount - windowPass <= maxCount) {
                return waitInMs;
            }
            //向后一个一个窗口
            earliestTime += windowLength;
            //当前一个窗口通过数量
            currentPass -= windowPass;
            idx++;
        }

        return OccupyTimeoutProperty.getOccupyTimeout();
    }

判断请求数量是否大于限流数量,如果超过限流数量,判断是否允许占用后面的请求如果可以则获取需要等待的时间,等待一定时间后可以通过,否则直接返回false发生限流

DegradeSlot
@Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
        throws Throwable {
        //是否降级
        DegradeRuleManager.checkDegrade(resourceWrapper, context, node, count);
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

public static void checkDegrade(ResourceWrapper resource, Context context, DefaultNode node, int count)
        throws BlockException {
        //获取降级规则
        Set<DegradeRule> rules = degradeRules.get(resource.getName());
        if (rules == null) {
            return;
        }

        for (DegradeRule rule : rules) {
            //检查是否可以通过
            if (!rule.passCheck(context, node, count)) {
                throw new DegradeException(rule.getLimitApp(), rule);
            }
        }
    }

我们看下DegradeRule几个核心属性

private static final int RT_MAX_EXCEED_N = 5;
private double count;//错误率或者是RT值
private int timeWindow;//时间窗
private int grade = RuleConstant.DEGRADE_GRADE_RT;//默认为RT时间

根据资源名称获取所有降级规则,检查是否发生降级

public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) {
        if (cut.get()) {
            return false;
        }
        //获取全局统计node
        ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(this.getResource());
        if (clusterNode == null) {
            return true;
        }
        //判断RT响应时间
        if (grade == RuleConstant.DEGRADE_GRADE_RT) {
            double rt = clusterNode.avgRt();
            //响应时间没有超过阈值
            if (rt < this.count) {
                passCount.set(0);
                return true;
            }

            // Sentinel will degrade the service only if count exceeds.
            //连接发生5次请求rt超过阈值的请求则发生降级
            if (passCount.incrementAndGet() < RT_MAX_EXCEED_N) {
                return true;
            }
            //降级规则为异常错误率
        } else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
            //异常请求数
            double exception = clusterNode.exceptionQps();
            //正常请求数
            double success = clusterNode.successQps();
            //请求总数
            double total = clusterNode.totalQps();
            // if total qps less than RT_MAX_EXCEED_N, pass.
            //请求总数<5直接通过
            if (total < RT_MAX_EXCEED_N) {
                return true;
            }
            //正常请求数中包含异常请求数量
            double realSuccess = success - exception;
            if (realSuccess <= 0 && exception < RT_MAX_EXCEED_N) {
                return true;
            }
            //计算异常请求率是否超过阈值
            if (exception / success < count) {
                return true;
            }
            //降级规则为判断异常请求数量
        } else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
            double exception = clusterNode.totalException();
            //异常请求没有达到阈值
            if (exception < count) {
                return true;
            }
        }
        //只会开启一次,开启定时任务经过timeWindow时间后将降级开关关闭
        if (cut.compareAndSet(false, true)) {
            ResetTask resetTask = new ResetTask(this);
            pool.schedule(resetTask, timeWindow, TimeUnit.SECONDS);
        }

        return false;
    }

这里根据配置的降级规则,一个是判断RT时间是否超过阈值,一个是根据错误率判断是否发生降级,另一个根据发送错误数量判断是否发生降级,如果发生降级则开启定时任务经过一段时间后将重新开启,服务又可以对外提供服务。发送降级则会抛出BlockException,然后StatisticSlot会根据FlowSlot和DegradeSlot的返回结果进行实时数据统计。导致里执行链的核心执行流程就分析完了。

数据分析案例-电影数据可视化分析 数据介绍数据为2011-2021电影数据可视化分析首先导入本次项目需要的包和数据 数据预处理 可视化 阅读详情

相关推荐

高通camera驱动 camx read xml 和 probe 流程 和 sensor I2C读写的波形图 (三)

Camx 刚接触的时候对于这个xml 一脸懵逼,实际上和mm-camera差不多就是以xml node节点的方式标准化了driver相关的code,exposure 和 gain的逻辑实现还是在同级目录的cpp中,并且将 xml 的解析工作放在了 out/target/<product>/gen/STATIC_LIBERARIES/libcamxgenerated_intermediates/generated/ 中主要是包含了: g_chromatix : tuning 相关xml的解析co

shangbolei的博客 1万+

链路追踪eagleEye介绍

淘宝现在是一个由很多个应用集群组成的非常复杂的分布式系统。这些应用里面主要有处理用户请求的前端系统和有提供服务的后端系统等。这些应用之间一般有RPC调用和异步消息通讯两种手段,RPC 调用会产生一层调一层的嵌套,一个消息发布出来更会被多个应用消费,另外,应用还会访问分库分表的数据库、缓存、存储等后端,以及调用其他外部系统如支付、物流、机彩票等。请试想一下,现在淘宝一个买家点击下单按钮所产生的网络请求到达淘宝服务器之后,就会触发淘宝内网数百次的网络调用。

qq_41358574的博客 2937

Clang Static Analyzer 使用教程:本地 + CMake + GitHub Actions 自动静态分析实战

本文介绍了ClangStaticAnalyzer静态代码分析工具的使用方法。该工具基于Clang/LLVM,可检测C/C++代码中的内存泄漏、空指针等常见错误。主要内容包括:安装指南(Ubuntu/macOS/Windows)、命令行使用方法、CMake项目集成方案、GitHub Actions自动化分析配置,以及HTML报告生成与查看。文章还提供了官方资源链接,帮助开发者系统掌握从本地调试到持续集成的完整静态分析工作流,提升代码质量保障能力。适用于需要在开发过程中实施静态分析的C/C++项目。

u014358031的博客 2156

Sentinel 原理:调用链

我们已经知道了sentinel实现限流降级的原理,其核心就是一堆Slot组成的调用链。 这里大概的介绍下每种Slot的功能职责: NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级; ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据; StatisticsSlot 则用于记录,统计不同维度的 ru

码农架构 1248

HTML粉色爱心(完整代码)

HTML语言实现粉色爱心的完整代码。

AIGC 1104

【报错解决】OpenClaw 报错 `openclaw: command not found`:全局命令未加入系统 PATH 的深度排查与修复指南

在国产AI工具链开发中,OpenClaw作为关键命令行工具常出现command not found报错。本文深度分析该问题的根源——Linux环境变量PATH未包含用户级安装目录(~/.local/bin)。通过四种解决方案(临时PATH修改、永久配置、软链接、虚拟环境处理)和排错Checklist,揭示"命令存在却找不到"的本质是路径配置问题,特别适用于Docker/国产Linux/非root环境。文中强调理解Linux工具链机制对国产AI开发的重要性,并指出这是开发者掌握环境

博客 5381

sentinel原理源码分析系列(五)-构建调用链路

上节分析构建插槽链,Sentinel的资源调用好比一个个连续的检查口,能否通过,检查规则和统计指标,插槽链肩负检查的责任,本章开始分析插槽,首先分析构建调用链路的两个插槽。

leehom的博客 787

135、转动惯量辨识方法

<think>我们要求根据内容生成≤150字的文章摘要。内容是关于电机控制中转动惯量辨识的方法和工程经验。摘要需简洁概括核心:转动惯量重要性、三种方法(加减速法、正弦扫频、MRAS)、工程注意点(摩擦补偿、采样同步等)、实用建议(组合使用、滤波限幅、安全保护)。字数严格≤150。需要提炼。</think>转动惯量辨识是电机控制的关键,负载变化会导致速度环失稳。工程常用加减速法(简单可靠)、正弦扫频法(高精度)、MRAS在线辨识。实操需补偿摩擦、同步采样、控制激励幅度。建议混合使用,辨识结果需滤波限幅,并设置

2401_88858637的博客 38

Sentinel 源码分析(二)

Slot ProcessorSlotChain public abstract class ProcessorSlotChain extends AbstractLinkedProcessorSlot<Object> { public abstract void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor); public abstract void addLast(AbstractLin.

demon7552003的小本本 374

47_AI智能体核心业务之全局主控Agent:智能对话系统的统一指挥中心

本文介绍了全局主控Agent的架构设计与实现,该组件作为智能对话系统的统一指挥中心,提供标准化的多模式处理接口。系统支持普通对话、工具命令执行和流式响应三种处理模式,通过智能工作流路由协调多个处理引擎。统一的消息封装和异步架构设计确保了系统的高性能和可扩展性,已成功处理300万次用户请求,平均响应时间1.2秒。

在科技的浪潮中,我们寻找着创新的火种,在代码的海洋里,我们编织着智慧的网。腾飞开源,就是这样一个由技术精英汇聚而成的博客平台,我们致力于分享在Java、Python、IoT和人工智能等领域的最新研究成果和实战经验。 在腾飞开源的博客上,你会看到紧跟技术前 338

【2025最新版】间接平差——以水准网平差为例 (python详细过程版)

水准网间接平差的python代码实现。博客长期更新,本文最新更新时间为:2025年5月20日。

点云侠的博客 1702

Sentinel源码分析(三) - 调用链路

@Author:zxw @Email:502513206@qq.com 目录 Sentinel源码分析(一) - 初识Sentinel Sentinel源码分析(二) - Entry构建​ 1.前言 之前已经分析过Entry的构造流程,我们知道Sentinel关于规则和流控的所有逻辑都在其内部的ProcessorSlotChain调用链路上,首先看看接口提供的方法有哪些。对于链表结构我们清楚,就是不断获取下一个节点,然后进行方法调用。这里该接口提供了两个方法分别为入口entry和出口exit。对于出口

V_zxw的博客 1691

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

《一起学sentinel》四、Slot的子类及实现之LogSlot和StatisticSlot

《一起学sentinel》四、Slot的子类及实现之LogSlot和StatisticSlot 一、概述 在 Sentinel 里面,所有的资源都对应一个资源名称(resourceName),每次资源调用都会创建一个 Entry 对象。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 SphU API 显式创建。Entry 创建的时候,同时也会创建一系列功能插槽(slot chain),这些插槽有不同的职责,例如: NodeSelectorSlot 负责收集资源的路径,并将这些资源

M4Y 474

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

1.8 源码分析

目录 Pt1 核心类解析 Pt1.1 Resource ResourceWrapper StringResourceWrapper Pt1.2 Entry Entry CtEntry Pt1.3 Context Pt1.4 Node Node StatisticNode DefaultNode ClusterNode EntranceNode Pt1.5 Slot NodeSelectorSlot ClusterBuilderSlot LogSlot Statistic

Java是世界上最好的语言 962

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

吃透阿里P8资深架构师写的Sentinel 源码解析 看完绝对涨技术!

提示:关注+转发本文+私信我“资料”,可获取更多电子书、技术教程、视频、大厂面试题等学习资料。 本文介绍阿里开源的 Sentinel 源码,GitHub: alibaba/Sentinel,基于当前(2019-12)最新的 release 版本 1.7.0。 简介 Sentinel 的定位是流量控制、熔断降级,你应该把它理解为一个第三方 Jar 包。 这个 Jar 包会进行流量统计,执行流量控制规则。而统计数据的展示和规则的设置在 sentinel- dashboard 项目中,这是一个...

sunjian286的专栏 1334

关于logUtils的工具类

package com.common.util /** * 日志输出工具类: LogUtil * @version LogUtil.java 2016年8月25日 下午12:01:48 */ public class LogUtil { /** * 摘要日志的内容分隔符 */ public static final String SEP = ",";

qq_38337370的博客 2361

Sentinel 的资源调用链原理分析

导读:本文来自Sentinel 源码爱好者逅弈的投稿,欢迎关注逅弈的个人公众号(逅弈逐码),让更多的个人开发者乐意、有动力参与到有价值的技术传播的工作中。通过此文,您将...

阿里巴巴中间件 997

sentinel context组件源码分析

前言 Context组件在sentinel中扮演的是一种什么样的角色呢?借用原作者的注释: This class holds metadata of current invocation 其实就是保存一次资源访问链路元数据的类,链路的各个节点都能通过获取链路绑定的context来获取一些信息进行相应的处理。很多涉及链路的框架都会有类似设计,例如netty的ChannelHandlerConte...

tinysakura的博客 1041

什么是Sentinel?它能做什么

Sentinel 是什么 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的轻量级流量控制产品,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助您保护服务的稳定性。 Sentinel 的历史 2012 年,Sentinel 诞生,主要功能为入口流量控制。 2013-2017 年,Sentinel 在阿里巴巴集团内部迅速发展...

潇兮水寒的博客 5万+

sentinel 来源访问控制

sentinel 来源访问控制

weixin_43931625的博客 905

SCA:二、Sentinel熔断、降级、限流

本文详解在Spring Cloud Alibaba环境下,通过Nacos与OpenFeign集成Sentinel实现服务降级的完整配置流程。重点包括:添加Sentinel依赖、开启Feign支持、定义降级兜底类(需@Component)、Feign接口配置fallback,并部署Sentinel Dashboard进行熔断限流规则配置。

博风_刺耳的博客(网络记事本) 224

Sentinel异常处理

sentinel触发异常的几种情况。

郭小李 164

第3篇:Sentinel熔断降级实战:大模型接口抖动时的服务兜底方案

本文针对多厂商LLM调用中因接口响应慢、服务异常导致线程池耗尽、系统雪崩的痛点,提出以Sentinel实现慢调用熔断降级。通过在LlmBizService中标注资源、配置2秒慢调用阈值与50%占比熔断规则,结合“重试→熔断”正确顺序,实现故障时快速拦截流量,保护核心业务。强调熔断与重试的协作关系、资源隔离、返回标准化及控制台动态调参等实战要点,为LLM场景提供稳定可靠的容灾能力。

qq_55253420的博客 308

Spring AI + GPT-5.5 + Ollama 配置教程:Base URL、超时与流式调用

由于不同 Spring AI、Spring Boot 小版本之间仍可能存在差异,创建项目时应检查 Spring Initializr、Spring AI BOM 和官方兼容矩阵,不要直接复制旧教程中的 Starter 名称和版本号。多人项目最好隔离不同客户端,并在脱敏前提下记录最终 URL、状态码、请求耗时、请求 ID 和模型 ID。Spring AI 接入模型 API,至少要同时确认 Starter、Base URL、请求路径、API Key、模型 ID、连接超时、读取超时和流式客户端这 8 项。

2601_95893075的博客 215

V8 机制、手写核心函数与 TypeScript 类型体操

或固定初始化(如数组先定长、对象先给全字段)保持类型与形状稳定;这些都比"换框架"更基础。只能处理纯数据(丢函数、Date、RegExp、循环引用报错)。生产级深拷贝要处理:原始值短路、循环引用、V8(Chrome / Node 的 JS 引擎)不是"解释一行跑一行"那么简单,它用。图:V8 执行流程——解释执行 + 热点优化,优化失败会 Deopt 回退。JS 内存自动管理,但"自动"不等于"不会漏"。是"固定间隔最多执行一次"。手写 Promise 是"懂异步"的试金石。是"停下后才执行最后一次",

qq_38468358的博客 326

【JVM】对象内存布局

HotSpot JVM 中,Java 对象内存布局由对象头(12字节,含Mark Word与压缩Klass Pointer)、实例数据及对齐填充构成,总大小为8字节倍数。对象头支持锁状态动态复用,指针压缩使引用仅4字节,但堆超32GB时失效。实例字段按类型优先级重排序以减少间隙,最小对象大小为16字节。通过JOL工具可精准验证布局,理解锁机制、GC、伪共享等底层行为。

2301_79732484的博客 140
上一篇: sentinel源码分析-04核心概念介绍
下一篇: sentinel源码分析-06热点参数限流
胖柯G
博客等级 码龄10年 453粉丝 57原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值