ProcessSlot构建流程分析

news2024/11/14 20:00:14

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 流量治理框架 - 编程界的小學生

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

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

相关文章

gorm day1

gorm day1 gorm简介gorm声明模型 代码样例基本来自官方文档 Gorm简介 什么是ORM&#xff1f; 对象关系映射(Objection Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库(如mysql数据库&#xff09;存在的互不匹配现象的计数。简单来说&#xff0c;ORM是通…

【annie/lux 快速下载哔哩哔哩视频】全网最简单,只需要5步!!!

1.首先 现在annie更名为lux 官网地址&#xff1a;https://github.com/iawia002/lux/releases 2.进入官网之后如图所示 3.下载lux软件 4.下载lux 这里需要说明一下 如果不下载这个的话也可以下载视频 但是视频和音频是分开的&#xff0c;你的视频没有声音 5.下载视频

04、全文检索 -- Solr -- 管理 Solr 的 core(使用命令和图形界面创建、删除 core,以及对core 目录下的各文件进行详细介绍)

目录 管理 Solr 的 core创建 Core方式1&#xff1a;solr 命令创建演示&#xff1a;使用 solr 命令创建 Core&#xff1a;演示&#xff1a;命令删除 Core&#xff08;彻底删除&#xff09; 方式2&#xff1a;图形界面创建Web控制台创建CoreWeb控制台删除 Core&#xff08;未彻底…

软件测试学习笔记-测试用例的编写

7中测试分类 按照阶段可划分单元测试、集成测试、系统测试、验收测试。代码可见度划分黑盒测试、灰盒测试、白盒测试 单元测试&#xff1a;针对源代码的测试 集成测试&#xff1a;针对接口进行测试 系统测试&#xff1a;针对功能和非功能的测试 验收测试&#xff1a;公测、内测…

js中执行上下文和执行栈是什么

文章目录 一、执行上下文二、生命周期创建阶段This Binding词法环境变量环境 执行阶段回收阶段 二、执行栈参考文献 一、执行上下文 简单的来说&#xff0c;执行上下文是一种对Javascript代码执行环境的抽象概念&#xff0c;也就是说只要有Javascript代码运行&#xff0c;那么…

关于Django部署

首先了解一下开发环境服务器跟生产环境服务器有何不同。 一、我们通过 python manage.py runserver 启动开发环境服务器&#xff0c;这条命令背后做了哪些事情&#xff1f; 1、首先加载Django项目的设置&#xff08;settings&#xff09; 2、检查数据库迁移&#xff0c;确保数…

这种学习单片机的顺序是否合理?

这种学习单片机的顺序是否合理&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「单片机的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01…

【Linux Day15 TCP网络通讯】

TCP网络通讯 TCP编程流程 接口介绍 socket()方法是用来创建一个套接字&#xff0c;有了套接字就可以通过网络进行数据的收发。创建套接字时要指定使用的服务类型&#xff0c;使用 TCP 协议选择流式服务&#xff08;SOCK_STREAM&#xff09;。 **bind()方法是用来指定套接字使…

绝地求生:海外博主呼吁PUBG2开发提上日程,PUBG2能否继往日荣光

海外PUBG博主WackyJacky101发推&#xff1a;PUBG 现在的平均玩家人数继续增加&#xff0c;但假期过后这里的势头似乎正在放缓&#xff01;现在是保持势头并宣布制作 PUBG 2 的最佳时机&#xff01; 大家好&#xff0c;我是闲游盒。PUBG作为最早的独立大逃杀游戏之一&#xff0c…

你今年过年回去吗?

#过年 我是一名21岁刚毕业的大学生&#xff0c;专业是软件技术&#xff0c;主修c#&#xff0c;之前在上海实习了一年&#xff0c;正式工作后来到了深圳&#xff0c;进入了一家电商公司实习。至于我为什么转行了&#xff0c;大家懂的都懂 现在是20240203晚上19.39&#xff0c;还…

算法设计与分析实验:回溯

目录 一、组合总和 1.1 具体思路 1.2 思路展示 1.3 代码实现 1.4 复杂度分析 1.5 运行结果 二、全排列 2.1 具体思路 2.2 思路展示 2.3 代码实现 2.4 复杂度分析 2.5 运行结果 三、N皇后问题 3.1 具体思路 3.2 思路展示 3.3 代码实现 3.4 复杂度分析 3.5 运行…

两个重要极限【高数笔记】

【第一个&#xff1a;lim &#xff08;sinx / x&#xff09; 1, x -- > 0】 1.本质&#xff1a; lim &#xff08;sin‘&#xff1f;’ / ‘&#xff1f;’&#xff09; 1, ‘&#xff1f;’ -- > 0&#xff1b;保证‘&#xff1f;’ -- > 0,与趋向无关 2.例题&#x…

Harbor介绍、整体架构和安装

1.Harbor介绍 Harbor 是由 VMware 开源的一款云原生制品仓库&#xff0c;Harbor 的核心功能是存储和管理 Artifact。Harbor 允许用户用命令行工具对容器镜像及其他 Artifact 进行推送和拉取&#xff0c;并提供了图形管理界面帮助用户查看和管理这些 Artifact。在 Harbor 2.0 版…

LangChain 81 LangGraph 从入门到精通三

LangChain系列文章 LangChain 60 深入理解LangChain 表达式语言23 multiple chains链透传参数 LangChain Expression Language (LCEL)LangChain 61 深入理解LangChain 表达式语言24 multiple chains链透传参数 LangChain Expression Language (LCEL)LangChain 62 深入理解Lang…

Logback学习

logback 1、logback介绍 Logback是由log4j创始人设计的另一个开源日志组件&#xff0c;性能比log4j要好。 lockback优点&#xff1a; 内核重写、测试充分、初始化内存加载更小&#xff0c;这一切让logback性能和log4j相比有诸多倍的提升。logback非常自然地直接实现了slf4j…

Open3d计算点云法向量,可视化(代码)

Open3d使用estimate_normals函数来计算法向量。其参数设置Open3d提供了3中参数搜索的方法&#xff08;所有计算的法向量模长为1&#xff09;&#xff1a; open3d.geometry.KDTreeSearchParamKNN(knn20) # 计算近邻的20个点 open3d.geometry.KDTreeSearc…

SVDiff: Compact Parameter Space for Diffusion Fine-Tuning——【论文笔记】

本文发表于ICCV 2023 论文地址&#xff1a;ICCV 2023 Open Access Repository (thecvf.com) 官方代码&#xff1a;mkshing/svdiff-pytorch: Implementation of "SVDiff: Compact Parameter Space for Diffusion Fine-Tuning" (github.com) 一、Introduction 最近几…

Apache POl Excel

目录 介绍 Apache POl的应用场景&#xff1a; 入门使用 通过POI创建Excel文件并且写入文件内容 通过POI读取Excel文件中的内容 介绍 Apache POl是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是&#xff0c;我们可以使用POI在Java程序中对Miscrosoft O…

AI应用开发-Visual Studio Code及Remote Development插件远程开发

AI应用开发相关目录 本专栏包括AI应用开发相关内容分享&#xff0c;包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概…

由vscode自动升级导致的“终端可以ssh服务器,但是vscode无法连接服务器”

问题描述 简单来说就是&#xff0c;ssh配置没动&#xff0c;前两天还可以用vscode连接服务器&#xff0c;今天突然就连不上了&#xff0c;但是用本地终端ssh可以顺利连接。 连接情况 我的ssh配置如下&#xff1a; Host gpu3HostName aaaUser zwx现在直接在终端中进行ssh&am…