当前位置: 首页 > news >正文

织梦 做网站 教程/代写企业软文

织梦 做网站 教程,代写企业软文,公司部门岗位职责,金融网站建设银行ProcessorSlot ProcessorSlot构建流程 // com.alibaba.csp.sentinel.CtSph#lookProcessChain private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)throws BlockException {// 省略创建 Context 的代码// 黑盒…

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接口文件来替代DefaultSlotChainBuilderif (defaultClass == null || clazz != defaultClass) {return createInstance(clazz);}}// 初始化默认的 DefaultSlotChainBuilderreturn loadDefaultInstance();
}

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

DefaultSlotChainBuilder()
// com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder
@Spi(isDefault = true)
public class DefaultSlotChainBuilder implements SlotChainBuilder {@Overridepublic ProcessorSlotChain build() {ProcessorSlotChain chain = new DefaultProcessorSlotChain();// 通过 SPI 的机制初始化责任链的那些 SlotList<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 {@Overridepublic 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.classprivate Class<S> service;// 私有构造器private SpiLoader(Class<S> service) {// 给 class 赋值this.service = service;}// -------------------- 方法 --------------------public static <T> SpiLoader<T> of(Class<T> service) {// 判断是不是 nullAssertUtil.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) 初始化 SpiLoaderSPI_LOADER_MAP.putIfAbsent(className, new SpiLoader<>(service));spiLoader = SPI_LOADER_MAP.get(className);}}}// 返回 SpiLoaderreturn spiLoader;}
}
loadFirstInstanceOrDefault()
// com.alibaba.csp.sentinel.spi.SpiLoader#loadFirstInstanceOrDefault
/*
加载第一个找到的提供程序实例,如果未找到,则返回默认提供程序实例
返回值:
提供程序实例
*/
public S loadFirstInstanceOrDefault() {// load()使用SPI机制从配置中获取ProcessSlotload();// 从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.classprivate 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 linecontinue;}line = line.trim();int commentIndex = line.indexOf("#");if (commentIndex == 0) {// Skip comment linecontinue;}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就给出提示, 如果没有, 那么久将类添加为defaltClassif (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>>() {@Overridepublic 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的ProcessSlotreturn 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()方法@Overridepublic 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 流量治理框架 - 编程界的小學生

相关文章:

ProcessSlot构建流程分析

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

工业笔记本丨行业三防笔记本丨亿道加固笔记本定制丨极端温度优势

工业笔记本是专为在恶劣环境条件下工作而设计的高度耐用的计算机设备。与传统消费者级笔记本电脑相比&#xff0c;工业笔记本在极端温度下展现出了许多优势。本文将探讨工业笔记本在极端温度环境中的表现&#xff0c;并介绍其优势。 耐高温性能: 工业笔记本具有更高的耐高温性…...

游戏服务器多少钱一台?腾讯云32元,阿里云26元

游戏服务器租用多少钱一年&#xff1f;1个月游戏服务器费用多少&#xff1f;阿里云游戏服务器26元1个月、腾讯云游戏服务器32元&#xff0c;游戏服务器配置从4核16G、4核32G、8核32G、16核64G等配置可选&#xff0c;可以选择轻量应用服务器和云服务器&#xff0c;阿腾云atengyu…...

实战分享:SpringBoot在创新创业项目管理中的应用

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…...

vue3:28— Vue 2 对 Vue 3 的所有非兼容性改变。(vue3学习笔记终)

非兼容性改变 | Vue 3 迁移指南 过渡类名v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from 。keyCode 作为 v-on 修饰符的支持。v-model 指令在组件上的使用已经被重新设计&#xff0c;替换掉了v-bind.sync.v-if 和 v-for 在同一个元素身上使用时的优先级发…...

【学习笔记】TypeScript学习笔记1 --TypeScript中的类型

文章目录 TS总的变量类型References TS总的变量类型 备注&#xff1a; 如果一个变量设置为了any 类型之后相当于变量关闭了TS的类型检测 let d: any; d 10; d hello;//unknown表示的是未知类型&#xff0c;实际是上一个安全的any,unknown类型的变量不能直接赋值给其他变量le…...

矩阵的正定(positive definite)性质的作用

1. 定义 注意&#xff0c;本文中正定和半正定矩阵不要求是对称或Hermite的。 2. 性质 3. 作用 &#xff08;1&#xff09;Axb直接法求解 cholesky实对称正定矩阵求解复共轭对称正定矩阵求解LDL实对称非正定矩阵求解复共轭对称非正定矩阵求解复对称矩阵求解LU实非对称矩阵求解…...

用python编写爬虫,爬取房产信息

题目 报告要求 工程报告链接放在这里 https://download.csdn.net/download/Samature/88816284使用 1.安装jupyter notebook 2.用jupyter notebook打开工程里的ipynb文件&#xff0c;再run all就行 注意事项 可能遇到的bug 暂无&#xff0c;有的话私信我...

Swift Combine 从入门到精通一

1. Combine 简介 用 Apple 官方的话来说&#xff0c;Combine 是: a declarative Swift API for processing values over time. Combine 是 Apple 用来实现函数响应式编程的库&#xff0c; 类似于 RxSwift。 RxSwift 是 ReactiveX 对 Swift 语言的实现。 Combine 使用了许多可以…...

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

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

企业飞书应用机器人,使用python发送图文信息到群

企业飞书应用的自动化&#xff0c;需要创建企业应用&#xff0c;应用开通机器人能力&#xff0c;并获取机器人所需的app_id与app_secret&#xff08;这一部分大家可以在飞书的控制台获取&#xff1a;https://open.feishu.cn/api-explorer/&#xff09; 文章目录 步骤1&#xff…...

设计模式1-访问者模式

访问者模式是一种行为设计模式&#xff0c;它允许你定义在对象结构中的元素上进行操作的新操作&#xff0c;而无需修改这些元素的类。这种模式的主要思想是将算法与元素的结构分离开&#xff0c;使得可以在不修改元素结构的情况下定义新的操作。 所谓算法与元素结构分离&#x…...

Android meminfo 查看方法及解析

目录 Android 上查看memory 信息的方法 内存限制的信息 手动释放缓存 例 adb shell dumpsys meminfo pid 解析 adb shell dumpsys meminfo 汇总信息说明 Total RAM Free RAM ION Used RAM Lost RAM ZRAM /proc/meminfo 参考文档 Android 上查看memory 信息的方法 …...

微信小程序解决华为手机保存图片到相册失败

1.新增隐私设置 2.优化代码 新增uni.authorize判断 _saveCode() {let that this;console.log(点击了保存图片)console.log(this.result)uni.authorize({scope: scope.writePhotosAlbum,success(e) {console.log(e)if (this.result ! "") {uni.saveImageToPhotosAlb…...

板块零 IDEA编译器基础:第三节 下载和在IDEA中集成 Tomcat服务器 来自【汤米尼克的JAVAEE全套教程专栏】

板块零 IDEA编译器基础&#xff1a;第三节 下载和在IDEA中集成 Tomcat服务器 一、为什么选择Tomcat&#xff08;1&#xff09;常见的JAVA WEB服务器&#xff08;2&#xff09;选择Tomcat的理由 二、Tomcat 8.5下载解压三、Tomcat 结构目录四、在IDEA中集成Tomcat 假设我们已经…...

2024/2/6

一、填空题 1、一个类的头文件如下所示&#xff0c;num初始化值为5&#xff0c;程序产生对象T&#xff0c;且修改num为10&#xff0c;并使用show()函数输出num的值10。 #include <iostream.h> class Test { private: static int num; public: Test(int); void sho…...

mysql清空表数据后如何让自增ID仍从1开始

有2种方法&#xff1a; 1、清空表时使用truncate命令&#xff0c;而不用delete命令 truncate test; 使用truncate命令的好处&#xff1a; 1&#xff09;、速度快 2&#xff09;、可以对自增ID进行重排&#xff0c;使自增ID仍从1开始计算 2、清空表数据后&#xff0c;使用alter…...

C++集群聊天服务器 数据模块+业务模块+CMake构建项目 笔记 (上)

跟着施磊老师做C项目&#xff0c;施磊老师_腾讯课堂 (qq.com) 本文在此篇博客的基础上继续实现数据模块和业务模块代码&#xff1a; C集群聊天服务器 网络模块业务模块CMake构建项目 笔记 &#xff08;上&#xff09;-CSDN博客https://blog.csdn.net/weixin_41987016/article…...

#Js篇:字符串的使用方法es5和es6

字符串 \ &#xff1a;单引号&#xff08;\u0027&#xff09;\" &#xff1a;双引号&#xff08;\u0022&#xff09; charAt 定义&#xff1a; 返回指定位置的字符&#xff0c;参数时从0开始编号的位置 参数&#xff1a; 位置下标 abc.charAt(1) // "b" …...

SpringBoo+Vue构建简洁日志文件查看系统

点击下载《SpringBooVue构建日志文件查看系统&#xff08;源代码&#xff09;》 1. 前言 想必经常做java开发的小伙伴&#xff0c;其大多数服务都是运行在linux系统上的&#xff0c;当遇到一些比较棘手的bug需要处理时&#xff0c;经常要上服务器去捞日志&#xff0c;然后通过…...

JavaScript基础第二天

JavaScript基础第二天 今天我们学习if分支语句、三元表达式和switch-case语句。 1. if分支语句 1.1 语法 if (条件表达式){// 满足条件要执行的语句 } else {// 不满足条件要执行的语句 }if中的内容如果为true&#xff0c;就执行大括号的代码块&#xff0c;如果为false执行…...

2、卷积和ReLU激活函数

python了解集合网络如何创建具有卷积层的特性。 文章目录 简介特征提取(Feature Extraction)卷积过滤(Filter with Convolution)Weights(权重)激活(Activations)用ReLU检测示例 - 应用卷积和ReLU结论In [1]: import numpy as np from itertools import productdef show_kerne…...

SQL世界之基础命令语句

目录 一、SQL SELECT 语句 1.SQL SELECT 语法 2.SQL SELECT 实例 3.SQL SELECT * 实例 二、SQL SELECT DISTINCT 语句 1.语法 2.使用 DISTINCT 关键词 三、SQL SELECT WHERE 语句 1.WHERE 子句 2.语法 3.使用 WHERE 子句 4.引号的使用 四、SQL SELECT AND&OR …...

Facebook未来展望:社交媒体的下一个篇章

社交媒体一直是连接人与人之间的纽带&#xff0c;而Facebook则一直在推动这一领域的发展。随着科技不断演进和社会需求的不断变迁&#xff0c;Facebook正积极筹谋社交媒体的下一个篇章。本文将深入剖析Facebook的未来展望&#xff0c;探讨其在社交媒体领域所迎接的新时代。 1. …...

源码搭建教学:直播带货商城小程序开发

结合小程序开发的直播带货商城&#xff0c;不仅可以提供更便捷的购物体验&#xff0c;还可以实现更高效的销售。因此&#xff0c;学习如何搭建一个直播带货商城小程序将成为您拓展商业领域的利器。 步骤一&#xff1a;准备工作 在开始开发之前&#xff0c;您需要进行一些准备工…...

vue-cli引入本地json数据:封装为js文件,无需请求直接读取

vue-cli引入本地json数据 1、新建js文件&#xff08;路径自定义&#xff09;&#xff0c;写入JSON数据 /* jsonData.js */export let jsonData { // 声明变量&#xff0c;存储数据// JSON源数据 }2、组件内引入js文件&#xff0c;读取数据 /* Example.vue */import { json…...

20240202在Ubuntu20.04.6下使用whisper.cpp的显卡模式

20240202在Ubuntu20.04.6下使用whisper.cpp的显卡模式 2024/2/2 19:43 【结论&#xff1a;在Ubuntu20.04.6下&#xff0c;确认large模式识别7分钟中文视频&#xff0c;需要356447.78 ms&#xff0c;也就是356.5秒&#xff0c;需要大概5分钟&#xff01;效率太差&#xff01;】 …...

前端面试拼图-数据结构与算法

摘要&#xff1a;总结一些前端算法题&#xff0c;持续更新&#xff01; 一、数据结构与算法 时间复杂度-程序执行时需要的计算量&#xff08;CPU&#xff09; 空间复杂度-程序执行时需要的内存空间 前端开发&#xff1a;重时间&#xff0c;轻空间 1.把一个数组旋转k步 arr…...

在C++的union中使用std::string(非POD对象)的陷阱

struct和union的对比 union最开始是C语言中的关键字&#xff0c;在嵌入式中比较常见&#xff0c;由于嵌入式内存比较稀缺&#xff0c;所以常用union用来节约空间&#xff0c;在其他需要节省内存的地方也可以用到这个关键字&#xff0c;写一个简单程序来说明union的用途 struc…...

Spring Cloud Netflix Eureka的参数调优

下面主要分为Client端和Server端两大类进行简述&#xff0c;Eureka的几个核心参数 客户端参数 Client端的核心参数 参数默认值说明eureka.client.availability-zones告知Client有哪些region以及availability-zones&#xff0c;支持配置修改运行时生效eureka.client.filter-o…...