Spring Cloud Alibaba - Sentinel源码分析

news2025/4/13 21:54:16

目录

一、Sentinel核心源码分析

1、Sentinel核心概念

1.1、Node之间的关系

2、Sentinel源码入口

2.1、SlotChain解析

2.2、NodeSelectorSlot解析

2.3、ClusterBuilderSlot解析


一、Sentinel核心源码分析

        Sentinel是分布式系统的防御系统。以流量为切入点,通过动态设置的流量控制、服务熔断等手段达到 保护系统的目的,通过服务降级增强服务被拒后用户的体验。

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

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

重要的概念:

  • slot chain:插槽
  • Node:根节点
  • Context:对资源操作时的上下文环境,每个资源操作(`针对Resource进行的entry/exit`)必须属于一个Context,如果程序中未指定Context,会创建name为"sentinel_default_context"的默认Context。一个Context生命周期内可能有多个资源操作,Context生命周期内的最后一个资源exit时会清理该Context,这也预示这真个Context生命周期的结束。
  • Entry:表示一次资源操作,内部会保存当前调用信息。在一个Context生命周期中多次资源操作,也就是对应多个Entry,这些Entry形成parent/child结构保存在Entry实例中

总体的框架

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

Sentinel源码下载

Sentinel源码地址

1、Sentinel核心概念

        Sentinel作为ali开源的一款轻量级流控框架,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。相比于Hystrix,Sentinel的设计更加简单,在 Sentinel中资源定义和规则配置是分离的,也就是说用户可以先通过Sentinel API给对应的业务逻辑定义资源(埋点),然后在需要的时候再配置规则,通过这种组合方式,极大的增加了Sentinel流控的灵活性。
        引入Sentinel带来的性能损耗非常小。只有在业务单机量级超过25W QPS的时候才会有一些显著的影响(5% - 10% 左右),单机QPS不太大的时候损耗几乎可以忽略不计。
Sentinel提供两种埋点方式:

  • try-catch 方式(通过 SphU.entry(...)),用户在 catch 块中执行异常处理 
  • if-else 方式(通过 SphO.entry(...)),当返回 false 时执行异常处理 

官方案例:

NodeSelectorSlot
这个 slot 主要负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级。

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

改写Demo

一个资源

import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;

public class ContextBySingleResourceDemo {
    public void ContextUtil(){
        //创建一个来自appA访问的Context
        //Context的名称为entrance1
        ContextUtil.enter("entrance1", "appA");
        // Entry就是一个资源操作对象
        Entry nodeA = null;
        try {
            //获取资源resource的entry
            nodeA = SphU.entry("resource1");//后续会展开这个位置
            // 如果代码走到这个位置,说明当前资源的请求通过了流控,可以继续进行相关业务处理
        } catch (BlockException e) {
            // 如果没有通过走到了这里,就表示请求被限流,这里进行降级操作
            e.printStackTrace();
        }finally {
            if (nodeA != null) {
                nodeA.exit();
            }
        }
        //释放Context
        ContextUtil.exit();
    }
}

多个资源

import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;

public class ContextByMultiResourceDemo {
    public void ContextUtil(){
        //创建一个来自appA访问的Context
        //Context的名称为entrance1
        ContextUtil.enter("entrance1", "appA");
        // Entry就是一个资源操作对象
        Entry nodeA = null;
        Entry nodeB = null;
        try {
            //获取资源resource1的entry
            nodeA = SphU.entry("resource1");
            // 如果代码走到这个位置,说明当前资源的请求通过了流控,可以继续进行相关业务处理

            //获取资源resource2的entry
            nodeB = SphU.entry("resource2");
            // 如果代码走到这个位置,说明当前资源的请求通过了流控,可以继续进行相关业务处理
        } catch (BlockException e) {
            // 如果没有通过走到了这里,就表示请求被限流,这里进行降级操作
            e.printStackTrace();
        }finally {
            if (nodeA != null) {
                nodeA.exit();
            }
            if (nodeB != null) {
                nodeB.exit();
            }
        }
        //释放Context
        ContextUtil.exit();
    }
}

1.1、Node之间的关系

  1. Node:接口,Sentinel 里面的各种种类的统计节点
  2. StatisticNode:统计节点,是Node的实现类,用于完成数据统计
  3. EntranceNode:DefaultNode的子类,入口节点,一个Context会有一个入口节点,用于统计当前Context的总体流量数据,统计维度为Context
  4. DefaultNode:默认节点,用于统计一个resource在当前Context中的流量数据,DefaultNode持有指定的Context和指定的Resource的统计数据,意味着DefaultNode是以Context和Resource为维度的统计节点
  5. ClusterNode:ClusterNode保存的是同一个Resource的相关的统计信息,是以Resource为维度的,不区分Context,这个是和DefaultNode的区别

Node之间的关系

Node 接口定义了一个 Node 类所需要提供的各项指标数据统计的相关功能,为外部屏蔽滑动窗口的存在。提供记录请求被拒绝、请求被放行、请求处理异常、请求处理成功的方法,以及获取当前时间窗口统计的请求总数、平均耗时等方法。

2、Sentinel源码入口

在微服务的使用Sentinel实际工作场景中,我们只需要引入对应依赖:spring-cloud-starter-alibaba-sentinel,就会进行自动装配,所以我们之间看META-INF/spring.factories,然后我们这里从SentinelAutoConfiguration开始看起。

        利用@SentinelResource注解作为切点,然后在通过AOP环绕通知,来进行增强,在执行原方法前,来执行对应操作,当然这里我们可以看出,一旦出现了限流或者限流就会走BlockException。

@EnableConfigurationProperties({SentinelProperties.class})
public class SentinelAutoConfiguration {
...
    @Bean
    @ConditionalOnMissingBean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
...
//----------------------------
@Aspect//切面
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
    public SentinelResourceAspect() {
    }
    //指定切入点为SentinelResource注解
    @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
    public void sentinelResourceAnnotationPointcut() {
    }
// 环绕通知
@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
    Method originMethod = resolveMethod(pjp);

    SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
    if (annotation == null) {
        // Should not go through here.
        throw new IllegalStateException("Wrong state for SentinelResource annotation");
    }
    String resourceName = getResourceName(annotation.value(), originMethod);
    EntryType entryType = annotation.entryType();
    int resourceType = annotation.resourceType();
    Entry entry = null;
    try {
        // 创建资源操作对象
        entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
        // 调用原方法
        return pjp.proceed();
    } catch (BlockException ex) {
        return handleBlockException(pjp, annotation, ex);
    } catch (Throwable ex) {
        Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
        // The ignore list will be checked first.
        if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
            throw ex;
        }
        if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
            traceException(ex);
            return handleFallback(pjp, annotation, ex);
        }

        // No fallback function can handle the exception, so throw it out.
        throw ex;
    } finally {
        if (entry != null) {
            entry.exit(1, pjp.getArgs());
        }
    }
}

在创建资源操作对象的时候我们需要先创建Context,但是明显这里没有显示创建,但是实际上我们如果看Context概念的话,就会知道,如果程序中未指定Context,会创建name为"sentinel_default_context"的默认Context,然后我们继续往下跟踪。

public class SphU {
...
    public static Entry entry(String name, int resourceType, EntryType type, Object[] args) throws BlockException {
        //限流方法
        return Env.sph.entryWithType(name, resourceType, type, 1, args);
    }

进入到entry方法中,这里的entryWithType方法就是我们要看的真正的限流的方法,具体的实现方法在com.alibaba.csp.sentinel.CtSph.entryWithType

public class CtSph implements Sph {
...
    public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, Object[] args) throws BlockException {
        return this.entryWithType(name, resourceType, entryType, count, false, args);
    }

    public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized, Object[] args) throws BlockException {
        // 这里将资源的名称和信息封装称为资源对象
        StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
        // 返回一个Entry资源操作对象
        // prioritized属性表示优先级,默认值为false,表示当前请求不按照优先级执行,直接执行
        return this.entryWithPriority(resource, count, prioritized, args);
    }

    private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) throws BlockException {
        // 从当前线程中获取Context
        // 一个请求会占用一个线程,并且绑定一个Context
        Context context = ContextUtil.getContext();
        // 一个请求对应一个Context
   	    // 如果当前类型为NullContext,表示此时请求已经超出了阈值,无需检测规则
        if (context instanceof NullContext) {
            return new CtEntry(resourceWrapper, (ProcessorSlot)null, context);
        } else {
            // 此时如果获取Context为空,就创建默认的sentinel_default_context,并且会放入到当前线程中
            if (context == null) {
                context = CtSph.InternalContextUtil.internalEnter("sentinel_default_context");
            }
            // 判断全局开关,如果是关闭状态,直接返回无需检测规则
            if (!Constants.ON) {
                return new CtEntry(resourceWrapper, (ProcessorSlot)null, context);
            } else {
                /**
                 * 这里是整个架构的核心所在,这里是在构建一个处理链,这个处理链是一个单向链表结构,类似于Filter一样,构建这个链条的
                 * 原因是对业务进行解耦,像限流资源保护有很多,比如限流、降级、热点参数、系统降级等等,如果都写在一起就耦合很严重,我们知道oop的
                 * 思想就是让每个类确定各自的职责,不要让他做不相干的事情,所以这里将业务进行全面解耦,然后在解耦的同时又通过链式编程将它们串起来
                 */
                ProcessorSlot<Object> chain = this.lookProcessChain(resourceWrapper);
                if (chain == null) {
                    return new CtEntry(resourceWrapper, (ProcessorSlot)null, context);
                } else {
                    CtEntry e = new CtEntry(resourceWrapper, chain, context);

                    try {
                        // 针对资源操作
                        chain.entry(context, resourceWrapper, (Object)null, count, prioritized, args);
                    } catch (BlockException var9) {
                        e.exit(count, args);
                        throw var9;
                    } catch (Throwable var10) {
                        RecordLog.info("Sentinel unexpected exception", var10);
                    }

                    return e;
                }
            }
        }
    }

InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);

    private final static class InternalContextUtil extends ContextUtil {
        static Context internalEnter(String name) {
            // 从这里继续跟踪
            return trueEnter(name, "");
        }

        static Context internalEnter(String name, String origin) {
            return trueEnter(name, origin);
        }
    }

首先这里要明确一下,一个Context的组成实际上需要name(名称)和origin(来源),所以方法上传入这两个参数

    protected static Context trueEnter(String name, String origin) {
        // 从当前线程中获取当前context名称
        Context context = contextHolder.get();
        // 如果当前context为空
        if (context == null) {
            // 从缓存中获取,当前缓存中key值为:Context名称,value值为:EntranceNode
            // (因为后续创建的是EntranceNode),需要它的原因是因为构建Context需要EntranceNode
            Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
            // 在缓存中获取EntranceNode
            DefaultNode node = localCacheNameMap.get(name);
            if (node == null) {
                // 当前缓存的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 {
                                // node赋值为EntranceNode
                                node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                                // Add entrance node.
                                // 将新建的EntranceNode添加到ROOT中
                                Constants.ROOT.addChild(node);
                                // 将新建的EntranceNode添加到缓存中
                                Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
                                newMap.putAll(contextNameNodeMap);
                                newMap.put(name, node);
                                contextNameNodeMap = newMap;
                            }
                        }
                    } finally {
                        LOCK.unlock();
                    }
                }
            }
            // 将name和node封装成Context
            context = new Context(node, name);
            // 设定来源
            context.setOrigin(origin);
            // 将context写入到当前线程中
            contextHolder.set(context);
        }
        // 返回Context
        return context;
    }

CtSph中,位置的chain.entry方法

//CtSph中entryWithPriority()
try {
    // 针对资源操作
    chain.entry(context, resourceWrapper, (Object)null, count, prioritized, args);
} catch (BlockException var9) {
    e.exit(count, args);
    throw var9;
} catch (Throwable var10) {
    RecordLog.info("Sentinel unexpected exception", var10);
}

官方定义:Sentinel 将 ProcessorSlot作为 SPI 接口进行扩展(1.7.2 版本以前 SlotChainBuilder作为 SPI),使得 Slot Chain 具备了扩展的能力。您可以自行加入自定义的 slot 并编排 slot 间的顺序,从而可以给 Sentinel 添加自定义的功能。

2.1、SlotChain解析

lookProcessChain()用于构建一个责任链。Sentinel的处理核心都在这个责任链中,链中每一个节点是一个Slot实例,这个链通过BlockException异常来告知调用入口最终的执行情况

//CtSph中entryWithPriority()
// 获取chain链
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
//-------------------------具体看lookProcessChain方法
    ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
        // 先从chainMap获取,若是存在,则直接返回
        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;
                    }
                    // 通过SlotChainProvider创建一个slot链
                    chain = SlotChainProvider.newSlotChain();
                    Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
                        chainMap.size() + 1);
                    newMap.putAll(chainMap);
                    // 添加到Map缓存中
                    newMap.put(resourceWrapper, chain);
                    chainMap = newMap;
                }
            }
        }
        return chain;
    }

这个位置我们要具体分析SlotChainProvider这个类型,它的主要作用就是通过已解析的槽链构建器,创建槽链的提供者

从这里我们可以看出SlotChainBuilder及ProcessorSlot 使用Java SPI技术实现可配置化,即在/META-INF/services/接口全限命名 的文件中配置实现类,然后由ServiceLoader实现加载

public final class SlotChainProvider {
...
    public static ProcessorSlotChain newSlotChain() {
        if (slotChainBuilder != null) {
            return slotChainBuilder.build();
        }
        // 读取配置文件在/META-INF/services/接口全限定命名的文件中配置实现类.
        // Resolve the slot chain builder SPI.
        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");
            //使用默认的DefaultSlotChainBuilder来构建ProcessorSlotChain
            slotChainBuilder = new DefaultSlotChainBuilder();
        } else {
            RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}",
                slotChainBuilder.getClass().getCanonicalName());
        }
        //使用DefaultSlotChainBuilder构建
        return slotChainBuilder.build();
    }

    private SlotChainProvider() {}
}

其实现在使用的是使用DefaultSlotChainBuilder.build()来创建的

在这个其中,做了几件事:
1. 创建DefaultProcessorSlotChain
2. 读取/META-INF/services/中的配置文件
3. 强制转型为AbstractLinkedProcessorSlot(所有插槽的抽象父类)

@Spi(isDefault = true)
public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build() {
        // 创建DefaultProcessorSlotChain
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
        // 读取配置文件在/META-INF/services/接口全限定命名的文件
        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;
    }
}

具体读取内容如下:

# Sentinel default ProcessorSlots
com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot
com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot
com.alibaba.csp.sentinel.slots.logger.LogSlot
com.alibaba.csp.sentinel.slots.statistic.StatisticSlot
com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot
com.alibaba.csp.sentinel.slots.system.SystemSlot
com.alibaba.csp.sentinel.slots.block.flow.FlowSlot
com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot
com.alibaba.csp.sentinel.slots.block.degrade.DefaultCircuitBreakerSlot

这些就是Sentinel提供的默认功能插槽

分析到这里我们就可以回到CtSph中,查看entry方法这个时候我们就知道了实际上调用entry方法的是DefaultProcessorSlotChain

//CtSph中entryWithPriority()
try {
    // 针对资源操作
    chain.entry(context, resourceWrapper, (Object)null, count, prioritized, args);
} catch (BlockException var9) {
    e.exit(count, args);
    throw var9;
} catch (Throwable var10) {
    RecordLog.info("Sentinel unexpected exception", var10);
}

那我们向下跟踪

public class DefaultProcessorSlotChain extends ProcessorSlotChain {
...
    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
        throws Throwable {
        // 转到下一个节点
        first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
    }

这个位置是转入到下一个节点,那么下一个节点明显就是NodeSelectorSlot

2.2、NodeSelectorSlot解析

public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
...
    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
@Spi(isSingleton = false, order = Constants.ORDER_NODE_SELECTOR_SLOT)
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {
    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 {
        // 从缓存中获取,创建DefaultNode
        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);
                    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);
                }

            }
        }

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

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

NodeSelectorSlot具体内容官网有给出解释:

这个 slot 主要负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级。

DefaultNode用于统计一个resource在当前Context中的流量数据,所以再结合NodeSelectorSlot,最终得出结论:处理不同的Context name,同一个Resource name的情况

2.3、ClusterBuilderSlot解析

官方定义:ClusterBuilderSlot:则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;

//NodeSelectorSlot.entry()
// 触发下一个节点
fireEntry(context, resourceWrapper, node, count, prioritized, args);

再触发下一个节点以后,调用的是父级AbstractLinkedProcessorSlot.fireEntry()方法,然后next调用transformEntry

//AbstractLinkedProcessorSlot
    @Override
    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);
        }
    }
//next就代表循环到下一个节点所以这里调用entry的就是ClusterBuilderSlot
@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);
    }

ClusterBuilderSlot

//ClusterBuilderSlot.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.
                    clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());
                    // key为资源 value为ClusterNode
                    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作用就是与DefaultNode进行关联,即不同的DefaultNode都关联了一个ClusterNode,这样我们在不同上下文中都可以拿到当前资源一个总的流量统计情况。

干我们这行,啥时候懈怠,就意味着长进的停止,长进的停止就意味着被淘汰,只能往前冲,直到凤凰涅槃的一天!

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

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

相关文章

001安装Jenkins

安装JenkinsJenkins 是一个开源自动化服务器http://www.jenkins.io/zh/doc/book/installing/#%E7%B3%BB%E7%BB%9F%E8%A6%81%E6%B1%82 docker docker run \-u root \--rm \-d \-p 8080:8080 \-p 50000:50000 \-v jenkins-data:/var/jenkins_home \-v /var/run/docker.sock:/va…

传感器融合概念及对比

1.多传感器融合的定义 传感器数据融合的定义可以概括为把分布在不同位置的多个同类或不同类传感器所提供的局部数据资源加以综合&#xff0c;采用计算机技术对其进行分析&#xff0c;消除多传感器信息之间可能存在的冗余和矛盾&#xff0c;加以互补&#xff0c;降低其不确实性…

记录好项目D2

记录好项目 你好呀&#xff0c;这里是我专门记录一下从某些地方收集起来的项目&#xff0c;对项目修改&#xff0c;进行添砖加瓦&#xff0c;变成自己的闪亮项目。修修补补也可以成为毕设哦 本次的项目是个旅游门户网站 技术栈&#xff1a;JSPjQueryAjaxechartsSpringSpring…

第四章LockSupport与线程中断

文章目录 线程中断机制面试题什么是中断机制?实现三种中断方式通过一个volatile变量实现通过AtomicBoolean&#xff08;原子布尔型&#xff09;通过Thread类自带的中断api方法实现 API源码分析当前线程的中断标识为true&#xff0c;是不是线程就立刻停止&#xff1f;后手案例-…

Vector-常用CAN工具 - Vector Hardware Manager

本文提供了有关 Vector 用于配置 Vector 接口的新工具Vector 硬件管理器(vHardwareManager) 的一些一般信息。 常见问题 1、什么是vHardwareManager&#xff1f; 2、哪些接口支持vHardwareManager&#xff1f; 3、什么时候需要vHardwareManager&#xff1f; 4、哪里可以下…

chatgpt赋能python:Python如何截图运行效果?

Python如何截图运行效果&#xff1f; 如果你是一位有10年python编程经验的工程师&#xff0c;那么你一定知道在编写程序时调试和调整非常重要。为了更好地调试程序&#xff0c;Python提供了许多进行程序运行效果截图的方法。本文将介绍几种常用的Python截图方法以及它们的优缺…

chatgpt赋能python:Python如何截图运行结果

Python如何截图运行结果 介绍 Python是一种高级编程语言&#xff0c;非常流行。它具有许多有用的功能和库&#xff0c;使其成为许多开发人员的首选编程语言之一。但是&#xff0c;当您运行Python程序并需要与他人共享结果时&#xff0c;您可能需要截图运行结果。在本文中&…

编译3D渲染引擎Horde3D

Horde3D是Github上一款开源的轻量级3D渲染引擎&#xff0c;同时它还支持多个平台。今天我们准备在Mac平台上交叉编译至Android平台。如果需要同时能编译Sample&#xff0c;那么还需要SDL2库。默认情况下&#xff0c;编译Horde3D时不强制下载SDL2&#xff0c;你可以选择强制下载…

chatgpt赋能python:Python怎么快速入门?

Python怎么快速入门&#xff1f; Python是一种易学易用的编程语言。它被广泛应用于各种领域&#xff0c;例如数据科学、自动化、Web开发、游戏开发等等。无论你是从事什么领域&#xff0c;在Python的快速入门上花费越少的时间越好。在本文中&#xff0c;我们将介绍Python的基础…

工作二--注意!!!

1、激活单元格&#xff0c;做数据回显 2、单元格退出编辑模式时&#xff0c;让 实时保存 3、获取数据时&#xff0c;用getGIUID 给数据 唯一id&#xff0c;以防数据名重复 数据结构&#xff1a;是结合接口的id 等 和 组件的数据结构 4、父子结构 下拉框中的 :value 把value值 …

网络安全如何6个月成功上岸?

学前感言: 1.这是一条坚持的道路,三分钟的热情可以放弃往下看了. 2.多练多想,不要离开了教程什么都不会了.最好看完教程自己独立完成技术方面的开发. 3.有时多 google,baidu,我们往往都遇不到好心的大神,谁会无聊天天给你做解答. 4.遇到实在搞不懂的,可以先放放,以后再来解…

Haproxy搭建Web高可用群集 【Keepalived+HAProxy 高可用 日志定义 内核优化】

Haproxy HAProxy是可提供高可用性、负载均衡以及基于TCP和HTTP应用的代理&#xff0c;是免费、快速并且可靠的一种解决方案。HAProxy非常适用于并发大&#xff08;并发达1w以上&#xff09;web站点&#xff0c;这些站点通常又需要会话保持或七层处理。HAProxy的运行模式使得它可…

Oracle 查询优化改写(第一章)

第一章 单表查询 1.查询空值 2.将空值转换为实际值 不采用nvl&#xff08;&#xff09;函数&#xff0c;而使用COALESCE函数语法为COALESCE(表达式1,表达式2,...,表达式n)&#xff0c;n>2,此表达式的功能为返回第一个不为空的表达式&#xff0c;如果都为空则返回空值。 注…

每日算法总结——回溯算法、 LeetCode 131. 分割回文串

LeetCode 131. 分割回文串 131. 分割回文串 - 力扣&#xff08;LeetCode&#xff09; 什么是回溯算法&#xff1f; 回溯算法真的是解决排列问题的一大利器&#xff0c;其实很多时候自己不经意间就写出了回溯算法&#xff0c;但是一直没有一个系统的认识&#xff0c;今天做一…

B树:数据结构中的平衡之道

目录 引言&#xff1a;一、定义&#xff1a;二、特点&#xff1a;三、应用场景&#xff1a;总结&#xff1a; 引言&#xff1a; 在计算机科学领域中&#xff0c;数据结构是构建和组织数据的重要工具。其中&#xff0c;B树&#xff08;B-tree&#xff09;作为一种自平衡的搜索树…

用RDMA重新思考有状态流处理

摘要 远程直接内存访问 (RDMA) 硬件弥合了网络和主要内存速度之间的差距&#xff0c;从而验证了网络通常是分布式数据处理系统中的瓶颈的常见假设。然而&#xff0c;高速网络并没有提供“即插即用”的性能&#xff08;例如&#xff0c;使用 IP-overInfiniBand&#xff09;&…

第二章 数据处理篇:transforms

教程参考&#xff1a; https://pytorch.org/tutorials/ https://github.com/TingsongYu/PyTorch_Tutorial https://github.com/yunjey/pytorch-tutorial 详细的transform的使用样例可以参考&#xff1a;ILLUSTRATION OF TRANSFORMS 文章目录 为什么要使用transformstransforms方…

RK3588平台开发系列讲解(以太网篇)PHY驱动

文章目录 一、PHY驱动初始化二、PHY 配置初始化三、PHY 的扫描四、PHY 的STATUS状态读取五、PHY的RESET 复位六、PHY 的注册phy_device_register七、PHY 的状态变化沉淀、分享、成长,让自己和他人都能有所收获!😄 一、PHY驱动初始化 完成了mdio总线的注册,以及对不一样厂家…

chatgpt赋能python:Python怎么截图

Python怎么截图 介绍 在日常的工作和学习中&#xff0c;截图是一项非常常见的操作。随着Python在各个领域的不断普及和应用&#xff0c;使用Python进行截图也成为了一个热门话题。Python作为一种高级编程语言&#xff0c;具备丰富的第三方库和模块&#xff0c;可以实现复杂的…

chatgpt赋能python:Python的GUI界面开发

Python的GUI界面开发 Python是目前非常流行的编程语言之一&#xff0c;其优雅的语法和强大的库使其在各个领域得到广泛应用。在GUI界面开发方面&#xff0c;Python也表现出了巨大的潜力。本文将介绍Python的GUI开发框架以及相关的工具和技术。 介绍 Python有多个GUI框架可供…