Seata 是如何兼容多个第三方配置中心的呢?

分布式事务 专栏收录该内容
5 篇文章 0 订阅

通过之前的几篇文章我们已经知道,Seata需要两个默认的配置文件file.conf和registry.conf在这里插入图片描述
registry.conf是seata配置文件的入口,主要存放注册中心的配置属性(registry {XXX})
和配置中心的属性值(config{XXX}),registry中的type默认为file,配置属性存放在file.conf里,如果 type 为其它类型,那么会从第三方配置中心加载配置属性值。
seata源码中,在config模块的core目录里有一个配置工厂类:
在这里插入图片描述
这里有一些静态常量:

	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";
    public static final String ENV_PROPERTY_KEY = "seataEnv";

    private static final String SYSTEM_PROPERTY_SEATA_CONFIG_NAME = "seata.config.name";
    private static final String ENV_SEATA_CONFIG_NAME = "SEATA_CONFIG_NAME";

    public static final Configuration CURRENT_FILE_INSTANCE;

REGISTRY_CONF_PREFIX、REGISTRY_CONF_SUFFIX:默认配置文件名、默认配置文件类型;
SYSTEM_PROPERTY_SEATA_CONFIG_NAME、ENV_SEATA_CONFIG_NAME、ENV_SYSTEM_KEY、ENV_PROPERTY_KEY:自定义文件名配置变量,这说明我们可以自定义配置中心的属性文件。

还有一处静态代码块:

static {
        String seataConfigName = System.getProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME);
        if (null == seataConfigName) {
            seataConfigName = System.getenv(ENV_SEATA_CONFIG_NAME);
        }
        if (null == seataConfigName) {
            seataConfigName = REGISTRY_CONF_PREFIX;
        }
        String envValue = System.getProperty(ENV_PROPERTY_KEY);
        if (null == envValue) {
            envValue = System.getenv(ENV_SYSTEM_KEY);
        }
        Configuration configuration = (null == envValue) ? new FileConfiguration(seataConfigName + REGISTRY_CONF_SUFFIX,
            false) : new FileConfiguration(seataConfigName + "-" + envValue + REGISTRY_CONF_SUFFIX, false);
        Configuration extConfiguration = null;
        try {
            extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration);
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("load Configuration:{}", extConfiguration == null ? configuration.getClass().getSimpleName()
                    : extConfiguration.getClass().getSimpleName());
            }
        } catch (EnhancedServiceNotFoundException ignore) {

        } catch (Exception e) {
            LOGGER.warn("failed to load extConfiguration:{}", e.getMessage(), e);
        }
        CURRENT_FILE_INSTANCE = null == extConfiguration ? configuration : extConfiguration;
    }

根据自定义文件名配置变量找出配置文件名称与类型,如果没有配置,默认使用 registry.conf。接着获取当前的配置实现类,core目录里的FileConfiguration 是 Seata 默认的配置实现类,它继承了 AbstractConfiguration,AbstractConfiguration实现了接口Configuration
在这里插入图片描述
在这里插入图片描述
Configuration提供了获取参数值的方法:

    short getShort(String dataId, short defaultValue);

    short getShort(String dataId);

    int getInt(String dataId, int defaultValue, long timeoutMills);

    int getInt(String dataId, int defaultValue);

    int getInt(String dataId);

    long getLong(String dataId, long defaultValue, long timeoutMills);
    ......

第三方的配置实现类亦是如此,已zk为例:
在这里插入图片描述
如果要利用 SPI 机制支持第三方扩展配置实现,需要继承 ConfigurationProvider 接口,在META-INF/services/创建一个文件并填写实现类的全路径名,如下所示:

@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);
        }
    }
}

在这里插入图片描述

然后静态代码块中的 EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration);
(EnhancedServiceLoader 是 Seata SPI 实现核心类),这行代码会加载 META-INF/services/和 META-INF/seata/目录中文件填写的类名,从而拿到配置实现类,那么如果其中有多个配置中心实现类都被加载了怎么办呢?
我们注意到 ZookeeperConfigurationProvider 类的上面有一个注解:

@LoadLevel(name = "ZK", order = 1)

在加载多个配置中心实现类时,会根据 order 进行排序:
io.seata.common.loader.EnhancedServiceLoader.findAllExtensionClass:

private List<ExtensionDefinition> findAllExtensionDefinition(ClassLoader loader) {
            List<ExtensionDefinition> extensionDefinitions = new ArrayList<>();
            try {
                loadFile(SERVICES_DIRECTORY, loader, extensionDefinitions);
                loadFile(SEATA_DIRECTORY, loader, extensionDefinitions);
            } catch (IOException e) {
                throw new EnhancedServiceNotFoundException(e);
            }

            //After loaded all the extensions,sort the caches by order
            if (!nameToDefinitionsMap.isEmpty()) {
                for (List<ExtensionDefinition> definitions : nameToDefinitionsMap.values()) {
                    Collections.sort(definitions, (def1, def2) -> {
                        int o1 = def1.getOrder();
                        int o2 = def2.getOrder();
                        return Integer.compare(o1, o2);
                    });
                }
            }

            if (!extensionDefinitions.isEmpty()) {
                Collections.sort(extensionDefinitions, (definition1, definition2) -> {
                    int o1 = definition1.getOrder();
                    int o2 = definition2.getOrder();
                    return Integer.compare(o1, o2);
                });
            }

            return extensionDefinitions;
        }

最终会获取优先级最高的那个实现类:
io.seata.common.loader.EnhancedServiceLoader.findAllExtensionClass:

private ExtensionDefinition getDefaultExtensionDefinition() {
            List<ExtensionDefinition> currentDefinitions = definitionsHolder.get();
            if (currentDefinitions != null && currentDefinitions.size() > 0) {
                return currentDefinitions.get(currentDefinitions.size() - 1);
            }
            return null;
        }

然后ZookeeperConfiguration它的构造方法如下:

	/**
     * Instantiates a new Zookeeper configuration.
     */
    public ZookeeperConfiguration() {
        if (zkClient == null) {
            synchronized (ZookeeperConfiguration.class) {
                if (null == zkClient) {
                    zkClient = new ZkClient(FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY),
                        FILE_CONFIG.getInt(FILE_CONFIG_KEY_PREFIX + SESSION_TIMEOUT_KEY, DEFAULT_SESSION_TIMEOUT),
                        FILE_CONFIG.getInt(FILE_CONFIG_KEY_PREFIX + CONNECT_TIMEOUT_KEY, DEFAULT_CONNECT_TIMEOUT));
                    String username = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + AUTH_USERNAME);
                    String password = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + AUTH_PASSWORD);
                    if (!StringUtils.isBlank(username) && !StringUtils.isBlank(password)) {
                        StringBuilder auth = new StringBuilder(username).append(":").append(password);
                        zkClient.addAuthInfo("digest", auth.toString().getBytes());
                    }
                }
            }
            if (!zkClient.exists(ROOT_PATH)) {
                zkClient.createPersistent(ROOT_PATH, true);
            }
        }
    }

这里的FILE_CONFIG就是上述静态代码块中最终创建的 registry.conf 配置实现类CURRENT_FILE_INSTANCE
在这里插入图片描述
至此,CURRENT_FILE_INSTANCE已经指向了最终的配置实现类,如果是默认配置那就是FileConfiguration如果是第三方那就是对应自定义的 xxxConfiguration,如ZookeeperConfiguration
然后在 Seata 项目中,获取一个第三方配置中心实现类通常是这么做的:

Configuration CONFIG = ConfigurationFactory.getInstance();

io.seata.config.ConfigurationFactory.getInstance:

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

io.seata.config.ConfigurationFactory.buildConfiguration:

private static Configuration buildConfiguration() {
        ConfigType configType;
        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);
        }
        if (ConfigType.File == configType) {
            String pathDataId = String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, FILE_TYPE, NAME_KEY);
            String name = CURRENT_FILE_INSTANCE.getConfig(pathDataId);
            Configuration configuration = new FileConfiguration(name);
            Configuration extConfiguration = null;
            try {
                extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration);
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("load Configuration:{}",
                        extConfiguration == null ? configuration.getClass().getSimpleName()
                            : extConfiguration.getClass().getSimpleName());
                }
            } catch (EnhancedServiceNotFoundException ignore) {

            } catch (Exception e) {
                LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e);
            }
            return null == extConfiguration ? configuration : extConfiguration;
        } else {
            return EnhancedServiceLoader.load(ConfigurationProvider.class, Objects.requireNonNull(configType).name())
                .provide();
        }
    }

我们看到这里seata还使用了根据name获取配置实现类的方法:

EnhancedServiceLoader.load(ConfigurationProvider.class, Objects.requireNonNull(configType).name())

其中的ConfigType 为配置中心类型,是个枚举类:

public enum ConfigType {
  File, ZK, Nacos, Apollo, Consul, Etcd3, SpringCloudConfig, Custom;
}

对应注解@LoadLevel(name = "ZK", order = 1)里的name

seata提供了第三方配置中心配置的同步脚本
在这里插入图片描述
config.txt 为本地配置好的值,搭建好第三方配置中心之后,运行对应脚本会将 config.txt 的配置同步到第三方配置中心。

参考链接:https://www.cnblogs.com/objcoding/p/12051212.html

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

紫x1静

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值