首页 > 其他分享 >sentinel学习笔记4-SPI 在 Sentinel 中的应用

sentinel学习笔记4-SPI 在 Sentinel 中的应用

时间:2024-12-22 21:03:35浏览次数:5  
标签:chain SPI resourceWrapper context Sentinel entry null sentinel

本文属于sentinel学习笔记系列。网上看到吴就业老师的专栏,写的好值得推荐,我整理的有所删减,推荐看原文。

https://blog.csdn.net/baidu_28523317/category_10400605.html

java SPI

SPI机制是Java平台提供的一种用于服务发现和服务提供者查找的机制。它允许在运行时动态地加载和实例化实现特定接口的类,从而达到扩展性和灵活性的目的,SPI 的本质是将接口实现类的全限定名配置在文件中,由服务加载器读取配置文件,加载实现类,实现在运行时动态替换接口的实现类。

适应场景:

数据库驱动管理、日志框架选择、框架扩展和插件机制、服务发现:SPI机制可以帮助系统在运行时发现可用的服务提供者,从而动态地选择和使用服务。

demo

找了之前的storage工程做个测试,新加了1个接口两个实现类

public interface TestService {
    void hello(String para);

}
@Service
public class AAATestServiceImpl implements TestService {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Override
    public void hello(String para) {
        logger.info("AAA.hello  para="+para);
    }
}
@Service
public class BBBTestServiceImpl implements TestService {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Override
    public void hello(String para) {
       logger.info("BBB.hello  para="+para);
    }
}

 测试:

@SpringBootTest
class TlmallStorageApplicationTests {

    @Test
    void contextLoads() {
        ServiceLoader<TestService> serviceLoader = ServiceLoader.load(TestService.class);
        for(TestService testService:serviceLoader){
            testService.hello("world");
        }
    }

}

 当我们想通过修改配置文件的方式而不修改代码实现权限验证框架的切换,就可以使用 Java 的 SPI。通过运行时从配置文件中读取实现类,加载使用配置的实现类。

需要在 resources 目录下新建一个目录 META-INF,并在 META-INF 目录下创建 services 目录,用来存放接口配置文件。然后在目录中创建一个文件,名称必须是Testservice定义的接口的全类路径名称。然后在文件中写上Testservice接口的实现类的全类路径名称。

当配置为:

org.tuling.tlmallstorage.service.impl.AAATestServiceImpl

测试输出

2024-12-22T11:50:14.265+08:00  INFO 5260 --- [tlmall-storage] [           main] o.t.t.service.impl.AAATestServiceImpl    : AAA.hello  para=world
当配置为:
org.tuling.tlmallstorage.service.impl.BBBTestServiceImpl

测试输出

配置多个就输出多个:

 

看下 java.util.ServiceLoader#load(java.lang.Class<S>)

    @CallerSensitive
    public static <S> ServiceLoader<S> load(Class<S> service) {
        //获取当前线程的上下文类加载器。
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        //使用获取到的类加载器、服务接口以及调用者的类信息,创建并返回一个新的 ServiceLoader 实例。
        return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
    }

 调用 load 方法传入接口名就能获取到一个 ServiceLoader 实例,此时配置文件中注册的实现类是还没有加载到 JVM 的,只有后面的在遍历迭代器的时候 ServiceLoader 才通过调用 Class#forName 方法加载类并且通过反射创建实例。

Java SPI 在 Sentinel 中的应用

在 sentinel-core 模块的resources下面,META-INF/services 目录,该目录下有三个以接口全名命名的文件

其中 com.alibaba.csp.sentinel.slotchain.SlotChainBuilder 文件用于配置 SlotChainBuilder 接口的实现类,com.alibaba.csp.sentinel.init.InitFunc 文件用于配置 InitFunc 接口的实现类,

com.alibaba.csp.sentinel.slotchain.ProcessorSlot 用于配置默认责任链。

以SlotChainBuilder为例。

以常见例子SphU.entry("XX") 为切入口,整个限流的核心操作在这里实现,代码在

com.alibaba.csp.sentinel.CtSph#entryWithPriority()

private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
        throws BlockException {
        Context context = ContextUtil.getContext();
        if (context instanceof NullContext) {
            // The {@link NullContext} indicates that the amount of context has exceeded the threshold,
            // so here init the entry only. No rule checking will be done.
            return new CtEntry(resourceWrapper, null, context);
        }

        if (context == null) {
            // Using default context.
            context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
        }

        // Global switch is close, no rule checking will do.
        if (!Constants.ON) {
            return new CtEntry(resourceWrapper, null, context);
        }
        //构建责任链
        ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

        /*
         * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
         * so no rule checking will be done.
         */
        if (chain == null) {
            return new CtEntry(resourceWrapper, null, context);
        }

        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;
        } catch (Throwable e1) {
            // This should not happen, unless there are errors existing in Sentinel internal.
            RecordLog.info("Sentinel unexpected exception", e1);
        }
        return e;
    }

核心逻辑就是构建责任链lookProcessChain与执行chain.entry。

其中看下构建责任链

com.alibaba.csp.sentinel.CtSph#lookProcessChain

  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.限制6000
                    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;
    }

chainMap里面获取slot功能链, 没有的话,就构建一个newSlotChain() 。

 public static ProcessorSlotChain newSlotChain() {
        if (slotChainBuilder != null) {
            return slotChainBuilder.build();
        }

        // Resolve the slot chain builder SPI. 通过SPI构建,返回第一个类型不等于 defaultClass 的实例
        slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();

        if (slotChainBuilder == null) {
            // Should not go through here. spi获取不到则使用默认的兜底
            RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
            slotChainBuilder = new DefaultSlotChainBuilder();
        } else {
            RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}",
                slotChainBuilder.getClass().getCanonicalName());
        }
        return slotChainBuilder.build();
    }

注意,这里Sentinel 在加载 SlotChainBuilder 时,只会通过loadFirstInstanceOrDefault获取第一个非默认(非 DefaultSlotChainBuilder)实现类的实例。

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

下面的官网的自定义slotdemo,实现请求 pass 后记录当前的 context 和资源信息

@Spi(order = -1500)
public class DemoSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
            throws Throwable {
        System.out.println("------Entering for entry on DemoSlot------");
        System.out.println("Current context: " + context.getName());
        System.out.println("Current entry resource: " + context.getCurEntry().getResourceWrapper().getName());

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

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        System.out.println("------Exiting for entry on DemoSlot------");
        System.out.println("Current context: " + context.getName());
        System.out.println("Current entry resource: " + context.getCurEntry().getResourceWrapper().getName());

        fireExit(context, resourceWrapper, count, args);
    }
}

配置spi:

 public static void main(String[] args) {
        Entry entry = null;
        try {
            entry = SphU.entry("abc");
        } catch (BlockException ex) {
            ex.printStackTrace();
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }

 输出:

INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: C:\Users\bohu8\logs\csp\
INFO: Sentinel log name use pid is: false
INFO: Sentinel log level is: INFO
------Entering for entry on DemoSlot------
Current context: sentinel_default_context
Current entry resource: abc
------Exiting for entry on DemoSlot------
Current context: sentinel_default_context
Current entry resource: abc

Process finished with exit code 0

标签:chain,SPI,resourceWrapper,context,Sentinel,entry,null,sentinel
From: https://blog.csdn.net/bohu83/article/details/144644368

相关文章

  • 【LTspice学习笔记】滤波电路设计
             本文是一个大三自动化专业本科生就无源/有源滤波器电路相关仿真设计的实验学习与记录过程,并对滤波器的一些原理特性以及亚诺德半导体公司开发的滤波器设计工具做简单介绍。一、设计要求(1)设计无源低通滤波器,其截止频率为482KHZ左右.(2) 设计N阶有源滤波器,......
  • 可扩展系统——基于SPI扩展
    一、我们为什么讨论SPI?为具有悠久历史的大型项目(屎山)添加新功能时,我们常常不太好评估变更的影响范围。因为原系统不具备良好的扩展性,导致修改整体发散,且不易单测。此时可以考虑使用接口来描述业务逻辑较为稳定的流程,并使用SPI机制来灵活的隔离加载实际的实现,来达到系统易于扩展的......
  • 将HTML转换为PDF:使用Spire.Doc的详细指南(二)无水印版
    目录引言一、准备工作1.下载Spire.DocforJava破解版2.将JAR包安装到本地Maven(1)打开命令提示符(2)输入安装命令(3)在pom.xml中导入依赖二、实现HTML到PDF的转换1.创建Java类2.完整代码示例3.代码解析4.处理图像5.性能优化6.错误处理与日志管理三......
  • 动画图解嵌入式常见的通讯协议:SPI、I²C、UART、红外
    文章下方附学习资源,自助领取。1SPI传输 ▲图1SPI数据传输 ▲图1.2SPI数据传输(2)  ▲图1.3SPI时序信号2I²C传输  ▲图1.2.1I2C总线以及寻址方式3年嵌入式物联网学习资源整理分享:C语言、Linux开发、数据结构;软件开发,STM32单片机、ARM硬......
  • UML活动图建模-官网案例-Hospital Management
    The ElectronicPrescriptionService enables prescribers -suchasgeneralpractitioners(GPs)andpracticenurses-tosendprescriptionselectronicallytoa dispenser (suchasapharmacy)ofthepatient'schoice.AswithallNHSConnectingforHeal......
  • QSpinBox & DoubleQSPinBox
    两个控件QSpinBox&DoubleQSPinBox都是QAbstractSpinBox的子类。其中我们不再举例DoubleQSPinBox ,因为其主要区别在于精度上,也就是说尤如其名DoubleQSPinBox 是double类型的我们来看看QSpinBox的几个主要功能。QSpinBox::value()//读取数据QSpinBox......
  • 题解:AT_abc236_f [ABC236F] Spices
    今天2024秋令营Day1的贪心例题,来解释一下这道题贪心的思路。首先输入一个整数\(n\),表示需要处理的数字数量为\(2^n-1\),随后输入每个编号的代价,并将代价和编号存储在数组\(a\)中。接着,对代价进行排序,以便在后续处理中优先选择代价较小的数字。然后,使用一个\(vis\)数......
  • Redis 哨兵 Sentinel 配置
    节点规划redis-01192.168.174.10816379redis-02192.168.174.11216379redis-03192.168.174.11716379chown-Rredis:redis/usr/local/redis/etc/redis主从配置在所有节点执行sed-i-e's@port6379@port16379@'-e's@bind127.0.0.1@bind0.0.0.0@g�......
  • Redis 哨兵 Sentinel 介绍
    RedisSentinel说明RedisSentinel为非集群Redis提供高可用解决方案。它能够在主节点发生故障时,自动切换主从角色,实现系统的高可用性。RedisSentinel还提供其他附带任务,例如监控、通知,并充当客户端的配置提供程序。RedisSentinel功能1.监控(Monitoring)分布式......
  • SPICE协议浅析
    本文分享自天翼云开发者社区《SPICE协议浅析》,作者:王****均云环境中根据使用场景不同,有多种远程控制台传输协议,如SPICE、VNC、RDP等。SPICE是SimpleProtocolforindependentComputingEnvironment的缩写,表示独立计算环境的简单协议。SPICE协议由三个基本部分组成:Spice协议,S......