阅读 271

SPI 在Sentinel中 的应用

SPI 在Sentinel中 的应用

SPI介绍

SPI全程是Service Provider Interface,直译就是服务提供者接口,是一种服务发现机制 ,是Java的一个内置标准,允许不同的开发者去实现某个特定的服务。SPI的本质是将接口实现类的全限定名配置在文件中,由服务加载器读取配置文件,加载实现类,实现在运行时动态替换接口的实现类。

使用 SPI 机制能够实现按配置加载接口的实现类,SPI 机制在阿里开源的项目中被广泛使用,例如 Dubbo、RocketMQ、以及Sentinel。

RocketMQ 与 Sentinel 使用的都是 Java 提供的 SPI 机制,而 Dubbo 则是使用自实现的一套 SPI,与 Java SPI 的配置方式不同,Dubbo SPI 使用 Key-Value 方式配置,目的是实现自适应扩展机制。

Java SPI 在 Sentinel 中的应用

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

  • com.alibaba.csp.sentinel.slotchain.SlotChainBuilder 文件用于配置 SlotChainBuilder 接口的实现类

  • com.alibaba.csp.sentinel.init.InitFunc 文件用于配置 InitFunc 接口的实现类

这两个配置文件中都配置了接口的默认实现类,如果我们不添加新的配置,Sentinel 将使用默认配置的接口实现类

com.alibaba.csp.sentinel.init.InitFunc 文件的默认配置如下:

com.alibaba.csp.sentinel.metric.extension.MetricCallbackInit    复制代码

com.alibaba.csp.sentinel.slotchain.SlotChainBuilder 文件的默认配置如下:

com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder复制代码

ServiceLoader 可加载接口配置文件中配置的所有实现类并且使用反射创建对象,但是否全部加载以及实例化还是由使用者自己决定。

Sentinel 的 core 模块使用 Java SPI 机制加载 InitFunc 与 SlotChainBuilder 的实现上稍有不同

如果 InitFunc 接口的配置文件注册了多个实现类,那么这些注册的 InitFunc 实现类都会被 Sentinel 加载、实例化,且都会被使用,但 SlotChainBuilder 不同,如果注册多个实现类,Sentinel 只会加载和使用第一个。

Sentinel 在加载 SlotChainBuilder 时,只会获取第一个非默认(非 DefaultSlotChainBuilder)实现类的实例,如果接口配置文件中除了默认实现类没有注册别的实现类,则 Sentinel 会使用这个默认的 SlotChainBuilder。其实现源码在 SpiLoader 的 loadFirstInstanceOrDefault 方法中。

//加载第一个找到的 Provider 实例,如果没有找到,返回默认的 Provider 实例
public S loadFirstInstanceOrDefault() {
    //加载provider实例
    load();

    for (Class<? extends S> clazz : classList) {
        if (defaultClass == null || clazz != defaultClass) {
            //加载找到的实例
            return createInstance(clazz);
        }
    }
    //加载默认实例
    return loadDefaultInstance();
}复制代码

Sentinel 加载 InitFunc 则不同,因为 Sentinel 允许存在多个初始化方法。InitFunc 可用于初始化配置限流、熔断规则

Sentinel 使用 ServiceLoader 加载注册的 InitFunc 实现代码如下

public final class InitExecutor {

    private static AtomicBoolean initialized = new AtomicBoolean(false);

    /**
     * 如果有一个InitFunc抛出异常,则 init 进程将立即中断,应用程序将退出。 初始化将只执行一次。
     */
    public static void doInit() {
        if (!initialized.compareAndSet(false, true)) {
            return;
        }
        try {
             // 加载配置
            List<InitFunc> initFuncs = SpiLoader.of(InitFunc.class).loadInstanceListSorted();
            List<OrderWrapper> initList = new ArrayList<OrderWrapper>();
            for (InitFunc initFunc : initFuncs) {
                 // 插入数组并排序,同时将 InitFunc 包装为 OrderWrapper
                insertSorted(initList, initFunc);
            }
            // 遍历调用 InitFunc 的初始化方法
            for (OrderWrapper w : initList) {
                w.func.init();
            }
        } catch (Exception ex) {
            RecordLog.warn("[InitExecutor] WARN: Initialization failed", ex);
            ex.printStackTrace();
        } catch (Error error) {
            RecordLog.warn("[InitExecutor] ERROR: Initialization failed with fatal error", error);
            error.printStackTrace();
        }
}复制代码

虽然 InitFunc 接口与 SlotChainBuilder 接口的配置文件在 sentinel-core 模块下,但我们不需要去修改 Sentinel 的源码,不需要修改 sentinel-core 模块下的接口配置文件,而只需要在当前项目的 /resource/META-INF/services 目录下创建一个与接口全名相同名称的配置文件,并在配置文件中添加接口的实现类即可。项目编译后不会覆盖 sentinel-core 模块下的相同名称的配置文件,而是将两个配置文件合并成一个配置文件。


作者:小林酱子
链接:https://juejin.cn/post/7025618832219701278


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐