Sentinel源码-NodeSelectorSlot和ClusterBuilderSlot

流控神器-Sentinel-资源节点树的构成(NodeSelectorSlot) 目录   1. 概述 2. 一些需要知道的前提 2.1. Resource 2.2. Context 2.3. Entry 2.4. Node 3. 深入分析 3.1. demo启动 3.2. 创建Context 3.3. 创建Entry 3.4. 执行NodeSelectorSlot.entry 3.4.1. 相同资源名情况下 3.4.2. 不同资源名情况下 1. ... 阅读详情

1. NodeSelectorSlot

第一个slot 是 NodeSelectorSlot开始,这个NodeSelectorSlot从命名上就能猜出来,做node选择的slot。看slot的源码,我们要时刻记着,一个资源对应一个slot链。这里我们先看下~NodeSelectorSlot·它的成员变量

/**
 * {@link DefaultNode}s of the same resource in different context.
 */
private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);

就是个map,存储着资源与node的对应关系

1.1 entry方法

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

    // 一个context name 对应一个defaultNode 对象
    // 从缓存中获取DefaultNode
    DefaultNode node = map.get(context.getName());
    // DCL
    if (node == null) {
        synchronized (this) {
            node = map.get(context.getName());
            if (node == null) {
                // 创建一个DefaultNode,并放入缓存map
                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
                // 将新建的node添加到调用树中
                ((DefaultNode) context.getLastNode()).addChild(node);
            }

        }
    }

    // 设置当前node
    context.setCurNode(node);
    // todo 触发下一个节点
    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

其实不关心业务含义的话,看这段代码很简单,无非就是从map中获取DefaultNode对象,如果不存在的话就创建。 但是我们不知道这个context是什么东西,它的name属性又是什么东西,有经验的程序员同学,一看到这个context就知道是个上下文,而且一般配合着ThreadLocal来使用,代码中倒数第二句把node设置到了这个context中,这个node就会跟着这个context一直往下传,但是我们知道这些是远远不够的,这时候我们就要结合之前的一些代码来看了:查看中4.3案例:

ContextUtil.enter(target, origin);
entry = SphU.entry(target, EntryType.IN);

这里解释下target跟origin,target的话就是当前资源,这里也就是你请求路径,origin,这个是上游的请求路径,在微服务中,上游服务调用下游服务,sentinel会通过一些手段将上游的资源信息带到下游服务中,这个不用太关心。 先看下这个ContextUtil.enter(target, origin);这段代码

protected static Context trueEnter(String name, String origin) {
    // 尝试从ThreadLocal中获取context
    Context context = contextHolder.get();
    // 若ThreadLocal中没有,则尝试从缓存map中获取
    if (context == null) {
        // 缓存map的key为context名称,value为EntranceNode
        Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
        // DCL 双重检测锁,防止并发创建对象
        DefaultNode node = localCacheNameMap.get(name);
        if (node == null) {
            // 若缓存map的size 大于 context数量的最大阈值,则直接返回NULL_CONTEXT
            if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                setNullContext();
                return NULL_CONTEXT;
            } else {
                LOCK.lock();
                try {

                    node = contextNameNodeMap.get(name);
                    if (node == null) {
                        if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                            setNullContext();
                            return NULL_CONTEXT;
                        } else {
                            // 创建一个EntranceNode
                            node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                            // Add entrance node.
                            // 将新建的node添加到Root
                            Constants.ROOT.addChild(node);

                            // 将新建的node写入到缓存map
                            // 为了防止"迭代稳定性问题"-iterate stable 对于共享集合的写操作
                            Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
                            newMap.putAll(contextNameNodeMap);
                            newMap.put(name, node);
                            contextNameNodeMap = newMap;
                        }
                    }
                } finally {
                    LOCK.unlock();
                }
            }
        }
        // 将context的name与entranceNode 封装成context
        context = new Context(node, name);
        // 初始化context的来源
        context.setOrigin(origin);
        // 将context写入到ThreadLocal
        contextHolder.set(context);
    }

别看很长,难度不大这段代码,首先是从contextHolder获取这个context,这个contextHolder就是个ThreadLocal ThreadLocal<Context> contextHolder = new ThreadLocal<>(); 这里想想,如果是项目刚启动,还没有请求过来,肯定是没有的,这个时候就会继续往下走,从一个缓存map中获取DefaultNode ,这个也是没有的,接着就是判断map的大小,不能超过2000,超过了就返回一个null context,不超过的话就会创建EntranceNode 对象,将这个资源传入构造,同时将新创建的EntranceNode 对象挂在ROOT 节点上,缓存到这个map中。这个ROOT节点其实也是个EntranceNode,不过它的资源名字是machine-root。接着往下看就是创建context了,然后将创建的context放到ThreadLocal中。

好了,到这里ContextUtil.enter(target, origin);这句代码就分析完成了。

接着分析下entry = SphU.entry(target, EntryType.IN);

private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
        throws BlockException {
        // 获取某个线程对应的context
        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);
        }
        // 如果是null的话
        if (context == null) {

            // Using default context.
            context = MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", resourceWrapper.getType());
        }

        // Global switch is close, no rule checking will do.
        if (!Constants.ON) {
            return new CtEntry(resourceWrapper, null, context);
        }
        ///创建 processor Slot
        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);
        }
        // 创建ctEntry对象
        Entry e = new CtEntry(resourceWrapper, chain, context);
        try {

            //chain  entry
            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;
    }

  • 先获取context,上面我们创建了context,这里直接获取就OK了。

  • 接着构建slot链,这里我们就不看了,之前都介绍过。

  • 接着创建CtEntry对象,这里没啥好看的

  • 最后就是进入slot链执行了。

到这里我们的context来龙去脉都能解释清楚了,但是node之间还是很乱,我们来画下关系

这个正好就是NodeSelectorSlot 类上面注释画的那个关系。

  • ROOT(machine-root) 是顶级的节点,也就是根节点
  • EntranceNodeOne是一个线程节点,属于context,同理EntranceNodeTwo
  • 而NodeSelectorSlot 里面维护的那个map,是相同资源不同context DefaultNode

1.2 exit

@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
    // 没干啥操作,接着往后调用
    fireExit(context, resourceWrapper, count, args);
}

1.3 总结

本文是第一个slot NodeSelectorSlot的源码解析,也是稍微简单的,代码不难,但是这里面Node的关系还是需要慢慢的体会

2. ClusterBuilderSlot

在第1小节中我们分析了第一个slot NodeSelectorSlot的源码,这个slot主要是为相同资源不同的线程创建node,缓存没有创建,然后塞到当前线程中。本小节分析的ClusterBuilderSlot 从名字上看也能知道是与集群有关的,它主要是为某个资源创建集群node的,也就是每个资源就一个集群node,代码也很简单,我们看下。

首先看下ClusterBuilderSlot的成员

private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>();//资源 ---》clusterNode

private static final Object lock = new Object();

private volatile ClusterNode clusterNode = null;// clusterNode

可以看到这个clusterNodeMap 其实就是个缓存 资源与 ClusterNode 对应关系的,一个资源对应着一个ClusterNode 对象。lock就是个锁,这个下面会用到,一个资源一把锁。clusterNode这个是对象成员,一个资源对应一个clusterNode。

2.1 entry方法

  @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.

                    // 创建 cluster node
                    clusterNode = new ClusterNode();
                    HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
                    newMap.putAll(clusterNodeMap);

                    ///node.getId() 这个其实是resource
                    newMap.put(node.getId(), clusterNode);

                    clusterNodeMap = newMap;
                }
            }
        }

        /// 设置clusterNode
        node.setClusterNode(clusterNode);

        /*
         * if context origin is set, we should get or create a new {@link Node} of
         * the specific origin.
         * 此上下文的起源(通常表示不同的调用者,例如服务使用者名称或起源IP)。
         */
        if (!"".equals(context.getOrigin())) {

            //创建originNode  然后在当前entry 甚至originNode
            Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
            context.getCurEntry().setOriginNode(originNode);
        }

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

整体步骤如下:

  • 就是这个资源对应的clusterNode不存在的话,就创建一个ClusterNode实例,然后放到map中缓存
  • 再就是设置到往当前node中,这个node是上个NodeSelectorSlot 创建的那个。
  • 再就是从context取出origin,这个origin得解释下,其实就是上层资源,比如说微服务中,上层服务调用下层服务,sentinel会通过一些手段将上层服务资源传到下层服务,然后就能获取到这个origin,如果我们这个服务不是上层服务的话,这个origin一般会有的,这里就是获取origin的node,他也有个map专门缓存的,如果没有就创建。- 最后将这个origin node塞到当前调用entry中。
  • 最后就是调用下一个slot。

2.2 exit方法

这个与NodeSelectorSlot 一样,啥事没干

@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
    fireExit(context, resourceWrapper, count, args);
}

2.3 总结

这个ClusterBuilderSlot 还是很简单,就是为资源创建一个clusternode ,然后塞到当前调用node中,同时将origin node 塞到当前调用entry中。其实从ClusterBuilderSlot 与NodeSelectorSlot 中我们会发现,开始它就是准备各个维度统计使用的node,这些维度还是需要我们自己品的。

 

Sentinel源码解析 整个Sentinel就是个大的try-catch,在服务端【dashboard】配置一系列规则,推到客户端内存的链条里面去,当我调用客户端方法的时候,都会经过链条,如果在这个过程中抛异常,会根据异常种类调用不同的对应的降级方法。一开始end指向first,end的next也就是first的next就指向了新加进来的slot,然后end重新指向新加进来的slot。中间的if逻辑是大概率不会走的。当这个链条也通过了,就会执行真正的业务方法========+++++++================= 阅读详情

相关推荐

避坑指南:Zabbix poller进程100%占用的5种常见原因及解决方案

本文深入剖析了Zabbix poller进程占用率高达100%的五大常见原因,包括监控项与进程数不匹配、不可达主机资源浪费、数据库性能瓶颈、监控项配置不合理以及版本缺陷。文章提供了从系统化诊断到针对性解决方案的完整指南,并重点介绍了如何通过优化zabbix_server.conf中的StartPollers等关键参数进行有效调优,帮助运维人员从根本上解决这一经典性能问题。

7z8x2c4v6b9的博客 250

7. NodeSelectorSlot

NodeSelectorSlot 负责为资源的首次访问创建 DefaultNode,以及维护 Context.curNode 和调用树。NodeSelectorSlot 被放在 ProcessorSlotChain 链表的第一个位置,这是因为后续的 ProcessorSlot 都需要依赖这个 ProcessorSlot。NodeSelectorSlot 源码如下: public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<

八荒六合唯我独尊 851

Sentinel--哨兵系列数据下载地址及流程更新【202310】

哨兵系列数据下载地址及流程更新。哨兵系列卫星作为哥白尼计划的一部分,主要目标便是对地球的观测任务。哥白尼计划由三部分组成:空间部分(哨兵系列太空任务与其他太空机构任务)、测量部分(提供海洋、大陆表面和大气信息的陆基和机载数据收集网络)、数据管理与全球共享。

twg666的博客 3万+

Sentinel源码分析四、NodeSelectorSlot

aa

vinylon的博客 643

泛微E9集成登录实战:5分钟搞定第三方系统单点登录配置(附避坑指南)

本文详细解析了泛微E9集成登录的实战配置流程与核心原理。通过理解其基于信任代理的身份传递机制,文章指导用户如何在5-10分钟内完成第三方系统的单点登录对接,涵盖基础配置、核心参数设置、高级表达式应用,并提供了编码问题、账号不一致等常见避坑指南,助力企业快速构建统一身份认证门户。

weixin_29185493的博客 290

在 Linux 虚拟机中手动安装 VMware Tools

对于 Linux 虚拟机,您可以从命令行手动安装 VMware Tools。对于 Linux 的较高发行版,请使用集成的 open-vm-tools 版本。 前提条件 有关 open-vm-tools 的操作系统兼容性的详细信息,请参见《VMware 兼容性指南》,网址为VMware Compatibility Guide - System Search。 开启虚拟机。 确认客户机操作系统正在运行。 因为 VMware Tools 安装程序是使用 Perl 编写的,请确认已在客户机操作系统中安..

m0_62526815的博客 1757

Sentine 源码分析之--NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot

前面我们对 Sentinel 有了一个比较系统的认知,本篇我们正式开始分析 Sentinel 的源码,我们知道 Sentinel 使用了责任链模式,根据 Sentinel 官方提供的流程图,我们知道 NodeSelectorSlot 是整个链路的入口,下面我们开始分析 Sentinel 源码。AbstractLinkedProcessorSlot#fireEntry 是一个通用方法,每个 Slot 执行完毕后都会调用这个方法,去判断是否有下一个 Slot。欢迎提出建议及对错误的地方指出纠正。

weixin_42118323的博客 1561

维护测试监控LLM应用

LangFuse: 开源 + SaaS(免费/付费),LangSmith 平替,可集成 LangChain 也可直接对接 OpenAI API;LangSmith: LangChain 的官方平台,SaaS 服务(免费/付费),非开源,企业版支持私有部署;调试 Prompt 的 Playground测试/验证系统的相关指标数据集管理各种指标监控与统计:访问量、响应时长、Token 费等等。

SunnyRivers 1780

看源码后总结的Sentinel原理

sentinel是把不同功能的slot通过SPI的方式按照order属性顺序组成一个责任链进行数据的统计和规则的判断.

2301_79290793的博客 392

DeepSpeed Zero3 Offload实战:从GCC版本检查到环境配置的完整避坑指南

本文是一份针对DeepSpeed Zero3 Offload部署的完整避坑指南。针对训练中常见的`AttributeError: ‘DeepSpeedCPUAdam‘ object has no attribute ‘ds_opt_adam`报错,文章深入剖析了其根源在于GCC编译器版本过低。指南提供了从系统GCC版本深度探查、安全启用高版本GCC环境,到DeepSpeed一致性安装验证的全链路实战方案,旨在帮助用户构建稳定的大模型训练环境。

weixin_29070721的博客 474

RK3588内核崩溃日志捕获:pstore与ramoops的实战配置指南

本文详细介绍了在RK3588嵌入式设备上配置pstore与ramoops以捕获内核崩溃日志的实战指南。通过内核配置调整、设备树设置及高级调试技巧,帮助开发者有效定位系统崩溃原因,特别适合户外智能设备等无法长期连接调试器的场景。文章还提供了生产环境优化建议,确保日志捕获的可靠性与安全性。

weixin_29189363的博客 407

Sentinel 是如何做限流的

限流是保障服务高可用的方式之一,尤其是在微服务架构中,对接口或资源进行限流可以有效地保障服务的可用性和稳定性。 之前的项目中使用的限流措施主要是Guava的RateLimiter。RateLimiter是基于令牌桶流控算法,使用非常简单,但是功能相对比较少。 而现在,我们有了一种新的选择,阿里提供的Sentinel。 Sentinel 是阿里巴巴提供的一种限流、熔断中间件,与RateLimiter相比,Sentinel提供了丰富的限流、熔断功能。它支持控制台配置限流、熔断规则,支持集群限流,并可以将.

vivo互联网技术 2370

【突变检验方法一】MATLAB实现Pettitt突变检验

MATLAB实现Pettitt突变检验及贝叶斯突变检测等

WW、forever的博客 1万+

spring cloud alibaba-sentinel原理

sentinel核心概念介绍,源码分析以及滑动时间窗算法源码分析

weixin_38612401的博客 684

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

廖雪峰Python教程[完整版]

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

Sentinel限流内部原理

前言 限流是保障服务高可用的方式之一,尤其是在微服务架构中,对接口或资源进行限流可以有效地保障服务的可用性和稳定性。 之前的项目中使用的限流措施主要是Guava的RateLimiter。RateLimiter是基于令牌桶流控算法,使用非常简单,但是功能相对比较少。 而现在,我们有了一种新的选择,阿里提供的 Sentinel。 Sentinel 是阿里巴巴提供的一种限流、熔断中间件,与RateLimiter相比,Sentinel提供了丰富的限流、熔断功能。它支持控制台配置限流、熔断规则,支持集群限流,并可以将

ZERO 1630

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

sentinel 不显示项目_Sentinel 是如何做限流的

限流是保障服务高可用的方式之一,尤其是在微服务架构中,对接口或资源进行限流可以有效地保障服务的可用性和稳定性。之前的项目中使用的限流措施主要是Guava的RateLimiter。RateLimiter是基于令牌桶流控算法,使用非常简单,但是功能相对比较少。而现在,我们有了一种新的选择,阿里提供的 Sentinel。Sentinel 是阿里巴巴提供的一种限流、熔断中间件,与RateLimiter相比...

weixin_42316286的博客 494

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

sentinel限流_Sentinel限流内部原理

前言限流是保障服务高可用的方式之一,尤其是在微服务架构中,对接口或资源进行限流可以有效地保障服务的可用性和稳定性。之前的项目中使用的限流措施主要是Guava的RateLimiter。RateLimiter是基于令牌桶流控算法,使用非常简单,但是功能相对比较少。而现在,我们有了一种新的选择,阿里提供的 Sentinel。Sentinel 是阿里巴巴提供的一种限流、熔断中间件,与RateLimiter...

weixin_39785858的博客 6589

Sentinel源码分析学习

本文学自黑马微服务部分,由于参考文档很详细,就直接使用原文档进行分享!!!本文章仅用于学习和交流,如果出现侵权,请及时联系作者进行处理!例如,我们在order-service服务中,将的方法标记为一个资源。1)首先在order-service中引入sentinel依赖

缘友一世的博客 1011

Sentinel源码(三)slot解析

回顾 在Sentinel源码(二)入口方法分析中,我们遗留了插槽链相关的内容,本文将详细解析这块内容 关于插槽链:在sentinel-core包下面默认提供了8个插槽连(com.alibaba.csp.sentinel.slotchain.ProcessorSlot) # Sentinel default ProcessorSlots com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot com.alibaba.csp.sentinel.s

Ethan_199402的博客 1254

Spring Cloud Alibaba - Sentinel源码分析(一)

Sentinel是分布式系统的防御系统。以流量为切入点,通过动态设置的流量控制、服务熔断等手段达到 保护系统的目的,通过服务降级增强服务被拒后用户的体验。在 Sentinel 里面,所有的资源都对应一个资源名称以及一个 Entry。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API 显式创建;每一个 Entry 创建的时候,同时也会创建一系列功能插槽(slot chain)。资源操作时的上下文环境一次资源操作Sentinel 将。

杀神lwz的博客 1833

深扒Sentinel背后的实现原理之后,我终于明白它为什么这么强了

Sentinel是阿里开源的一款面向分布式、多语言异构化服务架构的流量治理组件。主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。上面两句话来自Sentinel官网的自我介绍,从这短短的两句话就可以看出Sentinel的定位和拥有的强大功能。​通过整篇文章的分析之后,再回头看看Sentinel的简介的内容,其实就能更好地理解Sentinel的定位和拥有的强大功能。

Javatutouhouduan的博客 289

《一起学sentinel》三、Slot的子类及实现之NodeSelectorSlot和ClusterBuilderSlot

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

M4Y 396

Sentinel源码分析

Context代表调用链路的上下文,贯穿一次调用链路中的所有资源(Entry),基于ThreadLocal实现。Context维持着入口节点(EntraceNode)、本次调用链路的curNode(当前资源节点)、调用来源(origin)等信息。后续的Slot都可以通过Context拿到DefaultNode或者ClusterNode,从而获取统计数据,完成规则判断。Context初始化的过程中,会创建EntraceNode、contextName就是EntraceNode的名称。

1846
上一篇: 来卷一卷Java中的一枚性能测试工具!
下一篇: SpringCloud实践系列:Ribbon负载均衡
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值