ProcessSlot构建流程分析

ProcessorSlot

ProcessorSlot构建流程

// com.alibaba.csp.sentinel.CtSph#lookProcessChain
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
    throws BlockException {
    
    // 省略创建 Context 的代码

    // 黑盒方法一:初始化责任链
    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

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

责任链的线程安全和性能问题

一条完整的责任链通常会对应一个或多个资源,一个资源能被成千上万的线程访问, 需要从安全和性能进行考虑

线程安全: 采用加锁的方式来初始化责任链

性能问题: 使用HashMap继续缓存

// 缓存,以资源为 key, 责任链为 value
private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
        = new HashMap<ResourceWrapper, ProcessorSlotChain>();

// 加锁,synchronized
synchronized ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
    ProcessorSlotChain chain = chainMap.get(resourceWrapper);
    if (chain == null) {
        // 构建(初始化)责任链
        chain = SlotChainProvider.newSlotChain();
        // 放到缓存当中
        chainMap.put(resourceWrapper, chain);
    }
    return chain;
}

上述仅仅是伪代码,实际存在很多的问题,比如:synchronized 加到方法上锁粒度太粗、直接将创建完的责任链放到全局 HashMap当中的用法是线程不安全的等问题。完整的代码我们放到核心源码剖析那块~

// com.alibaba.csp.sentinel.CtSph#lookProcessChain
/*
作用: 获取ProcessorSlotChain资源。新建ProcessorSlotChain如果资源不相关,将创建一个。
相同的资源(ResourceWrapper.equals(Object)) 将共享相同的ProcessorSlotChain在全球范围内,无论在哪个Context
*/
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;
}

sentinel的sentinel-core组件使用到了Java SPI机制(如果不知道什么时SPI的, 可以看一下博主的JDK和Spring的SPI机制原理分析)
在这里插入图片描述

核心源码

源码主要分为两个核心部分,具体如下:

  • 提升性能和保证线程安全性:将责任链存储在全局缓存中,并使用锁来初始化责任链。
  • 构建整条责任链的流程:Sentinel 利用Java SPI完成责任链的构建。
锁粒度缩小

原代码存在下述问题

  1. 锁粒度过大
  2. 全局 HashMap 缓存是多线程共享变量
  3. 也就是说在此方法操作这个HashMap的时候,其他方法也可以同时操作,因此,在方法上加锁仍然无法保证线程安全

优化

  1. 缩小锁粒度
  2. 操作HashMap时上锁
  3. 使用(快照变量CopyOnWrite)实现读写分离
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
    ProcessorSlotChain chain = chainMap.get(resourceWrapper);
    if (chain == null) {
        // 降低锁粒度,从之前方法修饰符那里拿走,放到这里。
        synchronized (LOCK) {
            chain = chainMap.get(resourceWrapper);
            if (chain == null) {
                // 构建(初始化)责任链
                chain = SlotChainProvider.newSlotChain();
                
                // 这块是解决 HashMap 全局缓存线程不安全的问题,
                // 先复制一个快照变量,然后对快照变量进行操作,操作完在重新引用给全局缓存
                Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(chainMap.size() + 1);
                newMap.putAll(chainMap);
                newMap.put(resourceWrapper, chain);
                chainMap = newMap;
            }
        }
    }
    return chain;
}
Java SPI 完成责任链的构建

Sentinel 设计了一个可扩展的功能。它并没有直接根据 SPI 机制初始化责任链,而是先通过 SPI 初始化 SlotChainBuilder,然后再通过 SlotChainBuilder 初始化完整的责任链

// com.alibaba.csp.sentinel.slotchain.SlotChainProvider#newSlotChain
public static ProcessorSlotChain newSlotChain() {
    // 如果存在,则直接返回
    if (slotChainBuilder != null) {
        return slotChainBuilder.build();
    }

    // 通过 SPI 的机制初始化 SlotChainBuilder
    // of()方法则是利用获取SPI读取到的service
    // loadFirstInstanceOrDefault(): 获取slot构建器
    slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();

    return slotChainBuilder.build();
}
loadFirstInstanceOrDefault()
// com.alibaba.csp.sentinel.spi.SpiLoader#loadFirstInstanceOrDefault
public S loadFirstInstanceOrDefault() {
    // SPI 机制获取类,且放到 classList 数组里
    load();

    // 循环遍历,进行初始化
    for (Class<? extends S> clazz : classList) {
        // 获取第一个非DefaultSlotChainBuilder类的实例
        // 这行代码就提供了很大的扩展性,也就说你业务系统接入 Sentinel 的时候可以自己写个SPI接口文件来替代DefaultSlotChainBuilder
        if (defaultClass == null || clazz != defaultClass) {
            return createInstance(clazz);
        }
    }
    // 初始化默认的 DefaultSlotChainBuilder
    return loadDefaultInstance();
}

核心行 if (defaultClass == null || clazz != defaultClass) ,这行代码的好处在于业务系统接入 Sentinel 的时候可以自己写个SPI接口文件来替代DefaultSlotChainBuilder(后续会有自定义教程)

DefaultSlotChainBuilder()
// com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder
@Spi(isDefault = true)
public class DefaultSlotChainBuilder implements SlotChainBuilder {
    @Override
    public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
		// 通过 SPI 的机制初始化责任链的那些 Slot
        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
        for (ProcessorSlot slot : sortedSlotList) {
            // 都放到 DefaultProcessorSlotChain 当中
            chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
        }
        return chain;
    }
}
责任链的顺序

比如ClusterBuilderSlot的前一个节点必须是NodeSelectorSlot,而StatisticSlot的前一个节点必须是 ClusterBuilderSlot,等等。如果顺序不对,则会产生意想不到的结果,外部系统自定义的 Slot 顺序可以通过 @Spi(isSingleton = false, order = Constants.ORDER_CLUSTER_BUILDER_SLOT)order属性进行设定,为了防止出现顺序 bug,可以直接将自定义的Slot放到最后

MATE-INF下的文件的记录看着也是有顺序的, 但是具体实例化的顺序并不是根据SPI文件的编写顺序来确定的。相反,每个Slot都有一个 @Spi注解,该注解有一个order属性用于设置顺序。在读取SPI文件时,系统会按照 order 属性进行排序,然后将它们放入一个 ArrayList 中。随后,在遍历该List时,系统会根据顺序进行实例化
在这里插入图片描述

@Spi中使用到排序常量如下

// com.alibaba.csp.sentinel.Constants
// 默认处理器插槽的顺序如下, 值越小
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;

上述的这些常见被ProcessSlot上@Spi注解引用, 最终影响ProcessSlot加载的顺序, 对应如下图

在这里插入图片描述

下图是官方给出ProcessorSlot加载顺序, 官方给出的是错误的, 不准确, 以代码和SPI配置文件中为准(即上图), 注意一下即可
在这里插入图片描述

有了顺序之后, 那么就可以将各个Slot之间是通过next属性进行关联的, 整体上就是一个单向链表

// 每个 Slot 都是 AbstractLinkedProcessorSlot 的子类
private AbstractLinkedProcessorSlot<?> next = null;
public void setNext(AbstractLinkedProcessorSlot<?> next) {
    this.next = next;
}

每个Slot的作用如下(其实前面也阐述过)

  1. NodeSelectorSlot:负责创建和维护资源调用树(资源调用关系),同时为入口节点(Entry)关联对应的统计节点(DefaultNode)。这样,Sentinel 不仅可以实现对资源调用链路的监控,而且还能统计每个资源的调用信息。
  2. ClusterBuilderSlot:负责根据资源的统计信息计算集群维度的统计数据。集群维度统计数据是从资源维度的统计数据中整合得到的,用于实现流量控制、熔断降级等功能,也就是某个资源在整个集群中的统计数据,不区分 Context。
  3. LogSlot:此 Slot 功能很简单,就是记录请求异常的日志,以提供用于故障排查。相当于啥也没干,直接调用下一个 Slot,如果报错了,则记录异常日志。
  4. StatisticsSlot:负责统计资源的调用数据,如成功调用次数、异常次数、响应时间等。这些统计数据可以用于分析资源的性能,也可以用于驱动其他 Slot(如 FlowSlot 和 DegradeSlot)的运行逻辑。这个很好理解,就是记录一秒钟内或者一分钟内某个接口请求量是多少、异常次数是多少等指标。
  5. AuthoritySlot:负责授权控制。它根据资源的授权规则来判断请求是否允许访问。如果请求不允许访问,AuthoritySlot 将抛出一个 AuthorityException 异常。
  6. SystemSlot:负责系统保护。它根据系统的保护规则(例如系统负载、CPU 使用率等)来判断请求是否需要被限制。如果请求需要被限制,SystemSlot 将抛出一个 SystemException 异常。
  7. FlowSlot:负责流量控制。它根据资源的流量控制规则(例如 QPS 限制)来判断请求是否需要被限流。如果请求需要被限流,FlowSlot 将抛出一个 FlowException 异常。
  8. DegradeSlot:负责熔断降级。它根据资源的熔断降级规则(例如异常比例、异常数等)来判断请求是否需要被降级。如果请求需要被降级,DegradeSlot 将抛出一个 DegradeException 异常。

可以发现一共8个 Slot,每个 Slot 实际上都是一个过滤器,它们各自承担着不同的职责。然而,这些 Slot 可以整体划分为两类,分别是指标数据采集类规则验证类

public class DefaultSlotChainBuilder implements SlotChainBuilder {
    @Override
    public ProcessorSlotChain build() {
        // 相当于 Slot 责任链管理器
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
        // 按照 order 属性进行排序且实例化,然后放到sortedSlotList当中。
        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
        // 遍历 已经排好序的 Slot 集合
        for (ProcessorSlot slot : sortedSlotList) {
            // 安全检查,防止你自己业务系统也写了一个 SPI 文件,但没按规定继承 AbstractLinkedProcessorSlot,这时候会出现错误,所以需要安全检查。
            if (!(slot instanceof AbstractLinkedProcessorSlot)) {
                continue;
            }
            // 构建单向链表, 让每个Slot的next引用指向下一个Slot的动作封装成addLast方法, 底层其实就是 slot.next = next;
            chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
        }
        // 最终将单向链表返回
        return chain;
    }
}

debug断点查看一下是否如此

断点打在com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilderreturn chain;, 然后启动sentinel-dashboard组件, 访问sentinel控制台页面
在这里插入图片描述

load()是如何加载SPI文件

load()过程是加载SPI文件过程, 而且sentinel SPI和JDK SPI原理一致(我可没有说Sentinel 为什么不直接采取 JDK 内置的 ServiceLoader 来实现 SPI 机制,而要自己造轮子单独写一套 SPI 的实现逻辑。其核心原因是 Sentinel 支持很多个性化的配置,比如 @Spi 注解支持 order 排序属性以及 isSingleton 是否单例属性。如果是单例则每个类全局只能实例化一次(通过 Map 缓存实现),如果不是单例,则每次都 new 一个新的对象)–配置代替参数, 读取多个SPI文件

  • META-INF/services 下SPI 文件有很多个, 比如com.alibaba.csp.sentinel.slotchain.SlotChainBuilder, com.alibaba.csp.sentinel.slotchain.ProcessorSlot等, 每个文件的名字都不同, 正常来说应该提供一个传入一个fileName的参数, 例如load(String fileName)来区分不同文件, 这种添加参数的方式是可以的, 但是sentinel和Java一样,采用一个配置代替参数

    • 接口全类名作为文件名, 实现全类类作为文件内容

    • 同时方法内定义一个常量标识要读取的文件目录

      • private static final String SPI_FILE_PREFIX = "META-INF/services/";
        

具体实现如下

  1. 每个接口文件都会真实对应到一个Java的 interface,也就是对应一份Class文件。那么在使用 load() 方法之前,先调用一个静态方法配置好你要初始化哪个接口,
    • 比如:SpiLoader.of(SlotChainBuilder.class), 这里 of() 方法就配置了一个SPI接口(后续会分析of()的实现)
  2. 配置好后,我们再用链式编程调用 load() 方法,load() 方法内部就很简单了,直接用配置的 Class.getName() 就获取到了此接口的全路径名,拼接上 SPI_FILE_PREFIX 这个文件夹前缀就是一份完整的文件了
// com.alibaba.csp.sentinel.slotchain.SlotChainProvider#newSlotChain
slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
of()

如果看过Java SPI的实现, 那么你就会发现本质上一模一样

public final class SpiLoader<S> {
    // -------------------- 属性 --------------------
    // SPI 文件夹路径
    private static final String SPI_FILE_PREFIX = "META-INF/services/";
    
    // 缓存,每个接口的 class 只需要new一次SpiLoader即可,new 完将其缓存起来
    // 注意看这一采用的是ConcurrentHashMap, 是线程安全
    private static final ConcurrentHashMap<String, SpiLoader> SPI_LOADER_MAP = new ConcurrentHashMap<>();
    
    // 配置接口的class,比如SlotChainBuilder.class
    private Class<S> service;
    
    // 私有构造器
    private SpiLoader(Class<S> service) {
        // 给 class 赋值
        this.service = service;
    }
    
    // -------------------- 方法 --------------------
    public static <T> SpiLoader<T> of(Class<T> service) {
        // 判断是不是 null
        AssertUtil.notNull(service, "SPI class cannot be null");
        // 判断是不是interface 或者 abtract类型
        AssertUtil.isTrue(service.isInterface() || Modifier.isAbstract(service.getModifiers()),
                "SPI class[" + service.getName() + "] must be interface or abstract class");

        // 先获取接口的全路径名,判断缓存里是否已经存在了
        String className = service.getName();
        SpiLoader<T> spiLoader = SPI_LOADER_MAP.get(className);
        // 缓存里没有的话则double-check机制去初始化SpiLoader,且放到缓存
        if (spiLoader == null) {
            synchronized (SpiLoader.class) {
                spiLoader = SPI_LOADER_MAP.get(className);
                if (spiLoader == null) {
                    // new SpiLoader<>(service) 初始化 SpiLoader
                    SPI_LOADER_MAP.putIfAbsent(className, new SpiLoader<>(service));
                    spiLoader = SPI_LOADER_MAP.get(className);
                }
            }
        }
        // 返回 SpiLoader
        return spiLoader;
    }
}
loadFirstInstanceOrDefault()
// com.alibaba.csp.sentinel.spi.SpiLoader#loadFirstInstanceOrDefault
/*
加载第一个找到的提供程序实例,如果未找到,则返回默认提供程序实例
返回值:
提供程序实例
*/
public S loadFirstInstanceOrDefault() {
    // load()使用SPI机制从配置中获取ProcessSlot
    load();

    // 从classList中获取defaltClass并实例化
    for (Class<? extends S> clazz : classList) {
        if (defaultClass == null || clazz != defaultClass) {
            return createInstance(clazz);
        }
    }

    return loadDefaultInstance();
}
load()
public final class SpiLoader<S> {   
    // SPI 文件夹路径 
    private static final String SPI_FILE_PREFIX = "META-INF/services/";

    // 配置接口的 class,比如 SlotChainBuilder.class
    private Class<S> service;
    // 存放类信息
    private final List<Class<? extends S>> classList = Collections.synchronizedList(new ArrayList<Class<? extends S>>());

    public void load() {
        // 省略其它代码...
        // 1. 读取com.alibaba.csp.sentinel.slotchain.ProcessorSlot文件值,都放到sortedClassList集合中
        String fullFileName = SPI_FILE_PREFIX + service.getName();
        ClassLoader classLoader;
        // 初始化加载器
        if (SentinelConfig.shouldUseContextClassloader()) {
            classLoader = Thread.currentThread().getContextClassLoader();
        } else {
            classLoader = service.getClassLoader();
        }
        if (classLoader == null) {
            classLoader = ClassLoader.getSystemClassLoader();
        }
        Enumeration<URL> urls = null;
        try {
            urls = classLoader.getResources(fullFileName);
        } catch (IOException e) {
            fail("Error locating SPI configuration file, filename=" + fullFileName + ", classloader=" + classLoader, e);
        }

        if (urls == null || !urls.hasMoreElements()) {
            RecordLog.warn("No SPI configuration file, filename=" + fullFileName + ", classloader=" + classLoader);
            return;
        }

        // 循环读取配置中的配置
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();

            InputStream in = null;
            BufferedReader br = null;
            try {
                in = url.openStream();
                br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
                String line;
                while ((line = br.readLine()) != null) {
                    if (StringUtil.isBlank(line)) {
                        // Skip blank line
                        continue;
                    }

                    line = line.trim();
                    int commentIndex = line.indexOf("#");
                    if (commentIndex == 0) {
                        // Skip comment line
                        continue;
                    }

                    if (commentIndex > 0) {
                        line = line.substring(0, commentIndex);
                    }
                    line = line.trim();

                    Class<S> clazz = null;
                    try {
                        // 获取类信息, 用于后续创建
                        clazz = (Class<S>) Class.forName(line, false, classLoader);
                    } catch (ClassNotFoundException e) {
                        fail("class " + line + " not found", e);
                    }

                    // 判断clazz是不是service的子类
                    if (!service.isAssignableFrom(clazz)) {
                        fail("class " + clazz.getName() + "is not subtype of " + service.getName() + ",SPI configuration file=" + fullFileName);
                    }
                    // 加入到集合中
                    classList.add(clazz);
                    // 获取Spi注解
                    Spi spi = clazz.getAnnotation(Spi.class);
                    // 判断Spi注解是否配置了别名
                    String aliasName = spi == null || "".equals(spi.value()) ? clazz.getName() : spi.value();
                    if (classMap.containsKey(aliasName)) {
                        Class<? extends S> existClass = classMap.get(aliasName);
                        fail("Found repeat alias name for " + clazz.getName() + " and "
                             + existClass.getName() + ",SPI configuration file=" + fullFileName);
                    }
                    classMap.put(aliasName, clazz);

                    // 判断Spi注解是否配置的default, 如果已经有defaultClass就给出提示, 如果没有, 那么久将类添加为defaltClass
                    if (spi != null && spi.isDefault()) {
                        if (defaultClass != null) {
                            fail("Found more than one default Provider, SPI configuration file=" + fullFileName);
                        }
                        defaultClass = clazz;
                    }

                    RecordLog.info("[SpiLoader] Found SPI implementation for SPI {}, provider={}, aliasName={}"
                                   + ", isSingleton={}, isDefault={}, order={}",
                                   service.getName(), line, aliasName
                                   , spi == null ? true : spi.isSingleton()
                                   , spi == null ? false : spi.isDefault()
                                   , spi == null ? 0 : spi.order());
                }
            } catch (IOException e) {
                fail("error reading SPI configuration file", e);
            } finally {
                closeResources(in, br);
            }
        }

        // 生成新的按照 order 排序集合
        sortedClassList.addAll(classList);
        // 根据Spi上的order进行排序, 值越小排越前面(即优先级越高)
        Collections.sort(sortedClassList, new Comparator<Class<? extends S>>() {
            @Override
            public int compare(Class<? extends S> o1, Class<? extends S> o2) {
                // 获取 Spi 注解
                Spi spi1 = o1.getAnnotation(Spi.class);
                // 获取 Spi 注解的 order 属性
                int order1 = spi1 == null ? 0 : spi1.order();

                Spi spi2 = o2.getAnnotation(Spi.class);
                int order2 = spi2 == null ? 0 : spi2.order();
				// 排序
                return Integer.compare(order1, order2);
            }
        });
    }
}
createInstance()和createInstance()
public final class SpiLoader<S> {
    // 缓存提供程序的singleton实例,key: 提供程序的classname,value: 提供程序实例
    private final ConcurrentHashMap<String, S> singletonMap = new ConcurrentHashMap<>();
    
    private S createInstance(Class<? extends S> clazz) {
        // 获取@Spi注解
        Spi spi = clazz.getAnnotation(Spi.class);
        boolean singleton = true;
        if (spi != null) {
            singleton = spi.isSingleton();
        }
        return createInstance(clazz, singleton);
    }

    private S createInstance(Class<? extends S> clazz, boolean singleton) {
        S instance = null;
        try {
            // 判断是否为单例的
            if (singleton) {
                // 尝试从缓存中获取
                instance = singletonMap.get(clazz.getName());
                // 如果缓存中没有, 那么就创建对象(利用双检锁保证线程安全)
                if (instance == null) {
                    synchronized (this) {
                        instance = singletonMap.get(clazz.getName());
                        if (instance == null) {
                            // clazz.newInstance()创建类, 然后转成servcie类型
                            // clazz是service接口的实现类, 这里即我们平常使用的 接口 xxx = new 实现类()的形式
                            instance = service.cast(clazz.newInstance());
                            // 放入缓存当中
                            singletonMap.put(clazz.getName(), instance);
                        }
                    }
                }
            } else { // 如果不是单例的, 就直接创建
                instance = service.cast(clazz.newInstance());
            }
        } catch (Throwable e) {
            fail(clazz.getName() + " could not be instantiated");
        }
        
        // 返回实例对象
        return instance;
    }
}
loadInstanceListSorted()以及createInstanceList

sentinel中的DefaultSlotChainBuilder中还使用到了loadInstanceListSorted(), 这里也给大伙分析一下

// com.alibaba.csp.sentinel.spi.SpiLoader#loadInstanceListSorted
List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
public final class SpiLoader<S> {
    // 缓存中已排序的Provider类, 还记得不, load()方法中将classList添加到sortedClassList中, 然后对sortedClassList进行了排序
    private final List<Class<? extends S>> sortedClassList = Collections.synchronizedList(new ArrayList<Class<? extends S>>());

    public List<S> loadInstanceListSorted() {
        // load()之前分析过, 不在啰嗦
        load();
		// createInstanceList()和loadFirstInstanceOrDefault()的区别
        // createInstanceList(): 按顺序加载添加到ProcessSlotChain的ProcessSlot
        // loadFirstInstanceOrDefault(): 加载第一个ProcessSlotChain或者@Spi注解中指定defaultd的ProcessSlot
        return createInstanceList(sortedClassList);
    }

    /*
    创建提供程序实例列表
	形参: clazzList -类类型的提供程序
	返回值: 提供程序实例列表
    */
    private List<S> createInstanceList(List<Class<? extends S>> clazzList) {
        if (clazzList == null || clazzList.size() == 0) {
            return Collections.emptyList();
        }
		clazzLis
        // 这里实际上还是遍历传入的sortedClassList, 然后依次调用createInstance()创建
        // createInstance()前边也是分析过了, 所以这里不在赘述
        List<S> instances = new ArrayList<>(clazzList.size());
        for (Class<? extends S> clazz : clazzList) {
            S instance = createInstance(clazz);
            instances.add(instance);
        }
        return instances;
    }
}
自定MyCustomSlotChainBuilder替换掉默认的DefaultSlotChainBuilder

不想使用AuthoritySlot,那么可以不加载这个插槽, 步骤如下

  1. META-INF/services 下创建一个名为 com.alibaba.csp.sentinel.slotchain.SlotChainBuilder (其实sentinel-core组件已经有了)的接口文件,值为此接口的实现类全路径。如下

    # 注释掉默认的SlotChainBuilder
    # com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder
    # 填写自己的定义的SlotChainBuilder
    com.whitebrocade.slots.MySlotChainBuilder
    
  2. sentinel-core下创建层级目录com.whitebrocade.slots

  3. 在刚刚创建的目录下创建一个类MySlotChainBuilder

    • 类上贴@Spi注解
    • 实现SlotChainBuilder接口下的build()方法, 初始化插槽链, 往插槽链中插入自己想要插槽即可
// 1. 类贴@Spi注解
@Spi
public class MySlotChainBuilder implements SlotChainBuilder { // 2. 实现SlotChainBuilder类
    // 3. 重写build()方法
    @Override
    public ProcessorSlotChain build() {
        // 4. 创建插槽链
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
        // 5. 往插槽链中添加一系列插槽
        chain.addLast(new NodeSelectorSlot());
        chain.addLast(new ClusterBuilderSlot());
        chain.addLast(new StatisticSlot());
        chain.addLast(new FlowSlot());
        return chain;
    }
}
总结
责任链的初始化

整条责任链的初始化分为两步骤:

  • 首先初始化Builder,负责管控责任链整体,Sentinel要求Builder只能存在一个,而且是外部系统优先原则,因此,我们可以自己编写一个以 com.alibaba.csp.sentinel.slotchain.SlotChainBuilder 接口为名称的SPI文件来替代默认的 DefaultSlotChainBuilder。
  • 然后通过Builder初始化完整的责任链,这里仍然是通过SPI机制进行初始化,因此也可以额外扩展,比如外部系统自定义一个Slot且插入到完整责任链当中,这里值得注意的是责任链是有顺序的,如果顺序没指定正确,则可能造成意想不到的效果。顺序可以通过@Spi注解的order属性设置,为了防止出现 bug,建议直接将自定义的 Slot 放到最后
sentine不采用JDK默认ServiceLoader实现SPI机制的原因

Q: Sentinel 为什么不直接采取 JDK 内置的ServiceLoader来实现 SPI 机制,而要自己造轮子单独写一套 SPI 的实现逻辑

其核心原因是 Sentinel 支持很多个性化的配置,比如 @Spi 注解支持 order 排序属性以及 isSingleton 是否单例属性。如果是单例则每个类全局只能实例化一次(通过 Map 缓存实现),如果不是单例,则每次都new一个新的对象

配置代替参数的思想

Sentinel和Java采取通过配置替代参数的方式值得学习

参考资料

通关 Sentinel 流量治理框架 - 编程界的小學生

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

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

相关文章

Rust 第一个rust程序Hello Rust️

文章目录 前言一、vscode 安装rust相关插件二、Cargo New三、vscode调试rustLLDB 前言 Rust学习系列。今天就让我们掌握第一个rust程序。Hello Rust &#x1f980;️。 在上一篇文章我们在macOS成功安装了rust。 一、vscode 安装rust相关插件 以下是一些常用的 Rust 开发插件…

Axure RP 网页版,让原型设计更高效

交互神器Axure RP是一种专业的快速原型设计工具&#xff0c;但Axure在用户体验上的缺陷也很明显。其设置交互方式相对繁琐&#xff0c;可视化不足、条件判断、变量、中继器等功能的使用需要陡峭的学习曲线。许多设计师正在寻找一个可以取代Axure的原型设计工具&#xff0c;即时…

Python IDE——PyCharm的下载与安装(2024)

目录 一、Python开发工具 二、下载PyCharm 三、安装PyCharm 四、使用PyCharm 一、Python开发工具 Python解释器捆绑了Python的官方开发工具——IDLE(Integrated Development and Learning Environment&#xff0c;集成开发和学习环境)。IDLE具备集成开发环境&#xff08;I…

MySQL 小技巧:xtrabackup 软件包的下载及安装

案例&#xff1a;xtrabackup 软件包的下载及安装 软件包下载&#xff1a;Index of /percona/centos/7/RPMS/x86_64/ CentOS7 默认的数据库版本比较老,因此建议使用 xtrabackup 2.4 版本 // CentOS7 默认的数据库版本比较老,因此建议使用 xtrabackup 2.4 版本 // 安装 CentOS7 默…

vue3 之 组合式API—reactive和ref函数

ref&#xff08;&#xff09; 作用&#xff1a;接收简单类型或者对象类型的数据传入并返回一个响应式的对象 核心步骤&#xff1a; 1️⃣ 从 vue 包中导入 ref 函数 2️⃣在 <script setup>// 导入import { ref } from vue// 执行函数 传入参数 变量接收const count …

【考研408】计算机网络笔记

文章目录 计算机网络体系结构计算机网络概述计算机网络的组成计算机网络的功能计算机网络的分类计算机网络的性能指标课后习题 计算机网络体系结构与参考模型计算机网络协议、接口、服务的概念ISO/OSI参考模型和TCP/IP模型课后习题 物理层通信基础基本概念奈奎斯特定理与香农定…

Java二维数组的遍历

目录 创建二维数组二位数组初始化二位数组的遍历分析 创建二维数组 public class TestArray05{public static void main(String[] args){//定义一个二维数组&#xff1a;int[][] arr new int[3][];//本质上定义了一个一维数组&#xff0c;长度为3int[] a1 {1,2,3};arr[0] a…

java hutool工具类实现将数据下载到excel

通过hutool工具类&#xff0c;对于excel的操作变得非常简单&#xff0c;上篇介绍的是excel的上传&#xff0c;对excel的操作&#xff0c;核心代码只有一行。本篇的excel的下载&#xff0c;核心数据也不超过两行&#xff0c;简洁方便&#xff0c;特别适合当下的低代码操作。 下载…

vue2学习笔记(2/2)

vue2学习笔记&#xff08;1/2&#xff09; vue2学习笔记&#xff08;2/2&#xff09; 文章目录 1. 初始化脚手架2. 分析脚手架&render函数文件结构图示及说明main.jsindex.htmlApp.vueSchool.vueStudent.vue 关于不同版本的Vue修改默认配置vue.config.js配置文件 3. ref属…

百分点科技:《数据科学技术: 文本分析和知识图谱》

科技进步带来的便利已经渗透到工作生活的方方面面&#xff0c;ChatGPT的出现更是掀起了新一波的智能化浪潮&#xff0c;推动更多智能应用的涌现。这背后离不开一个朴素的逻辑&#xff0c;即对数据的收集、治理、建模、分析和应用&#xff0c;这便是数据科学所重点研究的对象——…

探索前端开发框架:React、Angular 和 Vue 的对决(二)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

使用Ettus USRP X440对雷达和EW系统进行原型验证

概览 无论是保障己方平台的生存能力&#xff0c;还是扰乱敌方频谱使用&#xff0c;以电磁(EM)频谱为主导都是任务成功的主要因素。电磁频谱操作(Electromagnetic Spectrum Operation, EMSO)需要使用战术系统来监测敌方的频谱活动、定位其发射器并帮助己方制定行动计划。软件无…

问题:魁奈在税收政策方面的主张是() #学习方法#其他#经验分享

问题&#xff1a;魁奈在税收政策方面的主张是&#xff08;&#xff09; A.征收农业税 B.征收工商业税 C.征收间接税 D.征收地租税 参考答案如图所示

网络协议梳理

1 引言 在计算机网络中要做到有条不紊地交换数据&#xff0c;就必须遵守一些事先约定好的规则。这些规则明确规定了所交换的数据的格式以及有关的同步问题。这里所说的同步不是狭义的&#xff08;即同频或同频同相&#xff09;而是广义的&#xff0c;即在一定的条件下应当发生什…

自学网安-IIS服务器

部署环境&#xff1a;win2003 配置环境&#xff1a;winxp ip&#xff1a;10.1.1.2 win2003 ip&#xff1a;10.1.1.1 开始安装 双击“应用程序服务器” 双击“Internet 信息服务&#xff08;IIS&#xff09;” 勾选万维网服务&#xff0c;确定然后下一步 查看端口号;netstat …

【新书推荐】5.2 位运算符

本节必须掌握的知识点&#xff1a; 位运算 示例十七 代码分析 汇编解析 5.2.1 位运算 位运算符如表5-2所示&#xff1a; 运算符 作用 示例 & 按位与 两个操作数同时为1&#xff0c;结果为1&#xff1b; | 按位或 两个操作数只要有一个为1&#xff0c;结果就为1&a…

参考数据集INRIA Holidays dataset

Download datasets 很贴心,MATLAB访问代码: % This function reads a siftgeo binary file % % Usage: [v, meta] = siftgeo_read (filename, maxdes) % filename the input filename % maxdes maximum number of descriptors to be loaded % (default=unlimit…

MySQL之SQL的查询语句如何执行

文章目录 前言一、一条查询的SQL是如何执行的二、MySQL的“零件”们1.连接器2.查询缓存3.分析器4.优化器5.执行器 总结 前言 做了多年技术之后&#xff0c;技术更新换代太快&#xff0c;从我入行时候从单体架构到后面分布式SOA&#xff0c;再到微服务&#xff0c;从后端再到全…

进程中线程使用率偏高问题排查

1. top命令查看CPU使用率高的进程 2. top -H -p 15931(进程PID) 查看进程下的线程 3. printf "%x\n" 17503(线程PID) 线程PID 10进制转16进制 0x445f 4. jstack -l 15931(JVM进程PID) 导出java进程栈信息&#xff0c;里面包含线程nid0x445f和所在的类&#xff0…

路由器、路由器的构成、交换结构

目录 1 路由器 1.1 路由器的结构 “转发”和“路由选择”的区别 1.1.1 输入端口对线路上收到的分组的处理 1.1.2 输出端口将交换结构传送来的分组发送到线路 2.2 交换结构 2.2.1 通过存储器 2.2.2 通过总线 2.2.3 通过纵横交换结构 (crossbar switch fabric) 1 路由器…