Seata 之 config 模块源码解读

本文首发于个人微信公众号《andyqian》,关注即可获取一线互联网内推机会!

前言

我们在编写项目过程中,配置文件几乎是标配,从最简单且最常用的 application.properties 文件,到现在集群环境下的配置中心。也算是互联网技术发展的一个缩影(单体应用到分布式应用)。在这方面,目前有非常多的开源实现,如:zookpeer,nacos,apollo 等等。像是命题作文下交付的不同答卷,各有优劣,当然,这有点偏题了。回到正题,今天讨论的话题是: Seata 中 config 模块的实现,主要会从以下几个方面进行分析:

  1. 职责。
  2. 设计
  3. 源码
  4. 设计模式

职责

config 模块承担的主要职责是:seata 框架配置文件的生命周期管理。这句话怎么理解呢?我们都知道,seata 框架的定位就是 分布式事务框架,从广义上来说也是中间件,而中间件就涉及到诸如:网络传输 (如:netty),传输就涉及到协议(如自定义的seata协议,http协议 …), 编解码 等等。恰恰对于中间件而言,在这方面应该予以用户更多的选择,而用户的个性化选择则在配置文件或配置中心体现。

设计

上面谈到了中间件应该予以用户更多的选择。在这方面:Microkernel + Plugin 模式是一个优秀的设计,其在 Dubbo 中有着非常好的实践。同样的:在 Seata 中,也有相同的设计,config 模块就是一个非常好的例子:

module 图

其中:

  1. seata-config-core 就是配置核心 module,值得注意的是:seata-config-all 是 config 模块一个父pom,而并不是一个Plugin。
  2. 其他的 module 则以 Plugin 形式存在,目前有:apollo, consul,zk,nacos 等等。

类图

  1. Configuration 接口是 config 模块一个非常重要的接口,所有配置相关的操作都围绕着该接口设计。不同的注册中心实现该接口,或者继承该接口的抽象类 AbstractConfiguration 类即可。其职责是:定义配置信息的原子操作,如:get,put, remove 等。类图如下所示:

 

 

 

需要注意的是: FileConfiguration 是 Seata 默认的配置实现。

除了Configuration 接口外,还有一个重要的接口就是:ConfigurationProvider,则表示 Configuration 能力的提供。类图如下所示:

 

 

入口

在 config 模块中,对外提供的入口是:ConfigurationFactory 类,通过其静态方法:getInstance() 对外提供 Configuration 接口的能力。

源码

通过上面的设计分析,现在我们来看看源码。

1. 首先,先看 Configuration 接口的源码:

public interface Configuration<T> {

 /**
  * Gets duration.
  * @param dataId       the data id
  * @param defaultValue the default value
  * @param timeoutMills the timeout mills
  * @return he duration
  */
 Duration getDuration(String dataId, Duration defaultValue, long timeoutMills);

/**
  * Gets config.
  * @param dataId       the data id
  * @param defaultValue the default value
  * @param timeoutMills the timeout mills
  * @return the config
  */
 String getConfig(String dataId, String defaultValue, long timeoutMills);

  /**
  * Put config boolean.
  * @param dataId       the data id
  * @param content      the content
  * @param timeoutMills the timeout mills
  * @return the boolean
  */
 boolean putConfig(String dataId, String content, long timeoutMills);

 /**
  * Put config if absent boolean.
  * @param dataId       the data id
  * @param content      the content
  * @param timeoutMills the timeout mills
  * @return the boolean
  */
 boolean putConfigIfAbsent(String dataId, String content, long timeoutMills);

  /**
  * Remove config listener.
  * @param dataId   the data id
  * @param listener the listener
  */
 void removeConfigListener(String dataId, T listener);

 /**
  * Gets config listeners.
  * @param dataId the data id
  * @return the config listeners
  */
 List<T> getConfigListeners(String dataId);

 /**
  * Gets config from sys pro.
  * @param dataId the data id
  * @return the config from sys pro
  */
 default String getConfigFromSysPro(String dataId) {
     return System.getProperty(dataId);
 }

...

备注:上述源码经过一些简单的筛选,实际还有很多重载函数,其底层调用的还是上述方法,故省略。

2. ConfigurationProvider 类的源码则非常简单,实例化对应的 ConfigurationProvider 即可。以 Zookeeper 为例,源码如下所示:

@LoadLevel(name = "ZK", order = 1)
public class ZookeeperConfigurationProvider implements ConfigurationProvider {
 @Override
 public Configuration provide() {
     try {
         return new ZookeeperConfiguration();
     } catch (Exception e) {
         throw new RuntimeException(e);
     }
 }

3. AbstractConfiguration 抽象类是 Configuration 接口的默认实现。除了 Configuration 定义的接口默认实现外,还定义了一个抽象方法:

public abstract class AbstractConfiguration<T> implements Configuration<T> {

    /**
     * The constant DEFAULT_CONFIG_TIMEOUT.
     */
    protected static final long DEFAULT_CONFIG_TIMEOUT = 5 * 1000;

    ... // 因篇幅原因,不在这里显示

     /**
     * Gets type name.
     *
     * @return the type name
     */
    public abstract String getTypeName();

4. ConfigurationFactory 类是使用 config 模块的入口,同时也是聚合,源码如下所示:

public final class ConfigurationFactory {
    private static final String REGISTRY_CONF_PREFIX = "registry";
    private static final String REGISTRY_CONF_SUFFIX = ".conf";
    private static final String ENV_SYSTEM_KEY = "SEATA_ENV";
    private static final String ENV_PROPERTY_KEY = "seataEnv";
    /**
     * the name of env
     */
    private static String envValue;

    // 加载配置文件
    static {
        envValue = System.getProperty(ENV_PROPERTY_KEY);
        if (null == envValue) {
            envValue = System.getenv(ENV_SYSTEM_KEY);
        }
    }

    //定义默认的 file configuration instant,
    private static final Configuration DEFAULT_FILE_INSTANCE = new FileConfiguration(
        REGISTRY_CONF_PREFIX + REGISTRY_CONF_SUFFIX);
    public static final Configuration CURRENT_FILE_INSTANCE = null == envValue ? DEFAULT_FILE_INSTANCE : new FileConfiguration(REGISTRY_CONF_PREFIX + "-" + envValue
        + REGISTRY_CONF_SUFFIX);
    private static final String NAME_KEY = "name";
    private static final String FILE_TYPE = "file";

    private static volatile Configuration instance = null;

    /**
     * // 单例获取 Configuration
     * @return the instance
     */
    public static Configuration getInstance() {
        if (instance == null) {
            synchronized (Configuration.class) {
                if (instance == null) {
                    instance = buildConfiguration();
                }
            }
        }
        return instance;
    }

    // 构造 Configuration 对象
    private static Configuration buildConfiguration() {
        ConfigType configType = null;
        String configTypeName = null;
        try {
            configTypeName = CURRENT_FILE_INSTANCE.getConfig(
                ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR
                    + ConfigurationKeys.FILE_ROOT_TYPE);
            configType = ConfigType.getType(configTypeName);
        } catch (Exception e) {
            throw new NotSupportYetException("not support register type: " + configTypeName, e);
        }
        //如果 configType = file,则构造 FileConfiguration 实例
        if (ConfigType.File == configType) {
            String pathDataId = ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR
                + FILE_TYPE + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR
                + NAME_KEY;
            String name = CURRENT_FILE_INSTANCE.getConfig(pathDataId);
            return new FileConfiguration(name);
        } else {
           // 否则通过 spi 进行加载service,并调用其 provide方法,例如:zookpeer, naocs , 等等。return EnhancedServiceLoader.load(ConfigurationProvider.class, Objects.requireNonNull(configType).name())
                .provide();
        }
    }

5. FileConfiguration 为 Seata 的默认 Configuration, 其底层依赖 typesafe.config 开源框架实现。源码如下所示:

public class FileConfiguration extends AbstractConfiguration<ConfigChangeListener> {

     private static final Logger LOGGER = LoggerFactory.getLogger(FileConfiguration.class);

    private final Config fileConfig;

    private ExecutorService configOperateExecutor;

    private ExecutorService configChangeExecutor;

    ....

      public FileConfiguration(String name) {
        if (null == name) {
            fileConfig = ConfigFactory.load();
        } else {
            //加载指定名称的 configName
            fileConfig = ConfigFactory.load(name);
        }
        // 构造配置获取的线程池
        configOperateExecutor = new ThreadPoolExecutor(CORE_CONFIG_OPERATE_THREAD, MAX_CONFIG_OPERATE_THREAD,
            Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
            new NamedThreadFactory("configOperate", MAX_CONFIG_OPERATE_THREAD));
        // 构造配置改变线程池,并启动, 每 1秒进行获取一次。configChangeExecutor = new ThreadPoolExecutor(CORE_CONFIG_CHANGE_THREAD, CORE_CONFIG_CHANGE_THREAD,
            Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
            new NamedThreadFactory("configChange", CORE_CONFIG_CHANGE_THREAD));
        configChangeExecutor.submit(new ConfigChangeRunnable());
    }
}

由于篇幅原因,config 模块的源码没有详细的列出,不过核心的源码如上所示。对于zookpeer, naocs 等的代码,则是按照 seata 定义的接口进行实现,可自行查看。

设计模式

在 config 模块中,使用到了多个设计模式,其中包括:

  1. ConfigurationFactory 类 中的 getInstance() 为典型的 单例模式
  2. Configuration 接口, AbstractConfiguration 抽象类,以及其子类 为典型的 适配器模式

config 模块的代码相对而言,还算容易理清楚。不过里面还涉及到一些例如:SPI 的设计,还没有分析,下次这个会单独摘成一篇文章呈现出来。如果你对源码也非常感兴趣,欢迎进群我们一起研读。


 

相关阅读:

接口设计的五点建议 !

Seata 分布式事务框架

Seata 之 rm-datasource 源码解读

Dubbo 线程池源码解析

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值