简介
分布式事务解决方案,找个成熟的方案看看,得谈谈seata。看看其源码,主要模块有core、server、tm、rm、tcc、config、discovery。。。,还挺多的,没办法要搞懂这个技术就得一个个看看,其实是看server的时候对config有了疑问,因此才转而想把config优先弄懂。
浅析
之前说到看server的时候有了疑问,那就是为什么分布式配置时nacos要用那个脚本(nacos-config.sh)把单值一个个配置项加载进nacos,而不是直接在nacos搞一个诸如seata.yml这样的文件配置呢?带着这个疑问开启seata-config探索之路,看看它的pom文件,不难看出seata-config-core就是其核心模块,然而知道核心模块好像也没啥太大作用啊,一看一堆接口和类,看一个都明白,看一堆就玩不转了。那这个简单了,看看server模块里边怎么用的,下边是server启动类的一部分代码。
public static void main(String[] args) throws IOException {
ParameterParser parameterParser = new ParameterParser(args);
NettyRemotingServer nettyRemotingServer = new NettyRemotingServer(WORKING_THREADS);
//server port
nettyRemotingServer.setListenPort(parameterParser.getPort());
//log store mode : file, db, redis
SessionHolder.init(parameterParser.getStoreMode());
DefaultCoordinator coordinator = new DefaultCoordinator(nettyRemotingServer);
coordinator.init();
nettyRemotingServer.setHandler(coordinator);
}
貌似没有config啥玩意啊,继续看看ParameterParser、NettyBaseConfig。
//ParameterParser
storeMode = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_MODE,
SERVER_DEFAULT_STORE_MODE);
//NettyBaseConfig
protected static final Configuration CONFIG = ConfigurationFactory.getInstance();
RANSPORT_SERVER_TYPE = TransportServerType.getType(CONFIG.getConfig(ConfigurationKeys.TRANSPORT_SERVER, TransportServerType.NIO.name()));
可以看到就是通过配置工厂去获取配置,然后通过配置去获取配置项。貌似config模块提供了工厂方法对外暴露Configuration来提供配置获取服务,那么好了正式进入正题,从ConfigurationFactory来研究config。
static {
load();
}
private static void load() {
String seataConfigName = System.getProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME);
if (seataConfigName == null) {
//registry
seataConfigName = REGISTRY_CONF_DEFAULT;
}
String envValue = System.getProperty(ENV_PROPERTY_KEY);
//1
Configuration configuration = (envValue == null) ? new FileConfiguration(seataConfigName,
false) : new FileConfiguration(seataConfigName + "-" + envValue, false);
Configuration extConfiguration = null;
try {
//2
extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration);
} catch (EnhancedServiceNotFoundException ignore) {
} catch (Exception e) {
LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e);
}
//3
CURRENT_FILE_INSTANCE = extConfiguration == null ? configuration : extConfiguration;
}
这段静态初始化代码就是搞一个CURRENT_FILE_INSTANCE对象,其中1是加载config.conf或其他通过环境指定的获取Configuration,2是通过spi机制增强Configuration,如SpringBootConfigurationProvider。继续看看如何获取单例的。
private static Configuration buildConfiguration() {
ConfigType configType;
String configTypeName;
try {
//1
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 e;
}
Configuration extConfiguration = null;
Configuration configuration;
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);
//2
configuration = new FileConfiguration(name);
try {
extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration);
} catch (EnhancedServiceNotFoundException ignore) {
} catch (Exception e) {
LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e);
}
} else {
//2
configuration = EnhancedServiceLoader
.load(ConfigurationProvider.class, Objects.requireNonNull(configType).name()).provide();
}
try {
Configuration configurationCache;
if (null != extConfiguration) {
configurationCache = ConfigurationCache.getInstance().proxy(extConfiguration);
} else {
//3
configurationCache = ConfigurationCache.getInstance().proxy(configuration);
}
if (null != configurationCache) {
extConfiguration = configurationCache;
}
} catch (EnhancedServiceNotFoundException ignore) {
} catch (Exception e) {
LOGGER.error("failed to load configurationCacheProvider:{}", e.getMessage(), e);
}
//4
return null == extConfiguration ? configuration : extConfiguration;
}
可以看到通过CURRENT_FILE_INSTANCE来获取configType,进而根据不同的configType来获取Configuration,2通过spi机制获取或增强Configuration,3来获取Configuration的代理对象,这个ConfigurationCache很简单就是提供一个本地缓存支持。
到这里ConfigurationFactory就差不多分析完了,简单总结下这个对象:1、静态初始化来加载配置文件(如registry.conf)来获取CURRENT_FILE_INSTANCE,并且通过spi提供扩展。2、由CURRENT_FILE_INSTANCE提供的configType来获取Configuration,并且做了代理处理,来提供缓存支持。
接下来就看看Configuration吧,这个接口很简单,就是对配置项的管理对象。看nacos的具体实现。
private static volatile ConfigService configService;
public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) {
String value = getConfigFromSysPro(dataId);
if (value != null) {
return value;
}
try {
value = configService.getConfig(dataId, getNacosGroup(), timeoutMills);
} catch (NacosException exx) {
LOGGER.error(exx.getErrMsg());
}
return value == null ? defaultValue : value;
}
这个就是委托给ConfigService,再看看ConfigService。
private final ClientWorker worker;
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
ConfigResponse cr = new ConfigResponse();
// 优先使用本地配置
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
if (content != null) {
cr.setContent(content);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
}
try {
String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);
cr.setContent(ct[0]);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
} catch (NacosException ioe) {
if (NacosException.NO_RIGHT == ioe.getErrCode()) {
throw ioe;
}
}
content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
cr.setContent(content);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
}
这个就是委托ClientWorker去拿配置,那继续看看ClientWorker吧。
public String[] getServerConfig(String dataId, String group, String tenant, long readTimeout)
throws NacosException {
String[] ct = new String[2];
if (StringUtils.isBlank(group)) {
group = Constants.DEFAULT_GROUP;
}
HttpRestResult<String> result = null;
try {
Map<String, String> params = new HashMap<String, String>(3);
if (StringUtils.isBlank(tenant)) {
params.put("dataId", dataId);
params.put("group", group);
} else {
params.put("dataId", dataId);
params.put("group", group);
params.put("tenant", tenant);
}
result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
} catch (Exception ex) {
String message = String
.format("[%s] [sub-server] get server config exception, dataId=%s, group=%s, tenant=%s",
agent.getName(), dataId, group, tenant);
}
switch (result.getCode()) {
case HttpURLConnection.HTTP_OK:
LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.getData());
ct[0] = result.getData();
if (result.getHeader().getValue(CONFIG_TYPE) != null) {
ct[1] = result.getHeader().getValue(CONFIG_TYPE);
} else {
ct[1] = ConfigType.TEXT.getType();
}
return ct;
}
}
这里就比较清楚了,就是到nacos去拿配置。但这个组件还有点内容,还要做个长轮询拉取。大致看看这个组件。
这个简单说吧,就是定时的拉取nacos服务端数据缓存到本地。
这里简单对nacos-config做下小结:NacosConfiguration委托ConfigService去获取配置,NacosConfigService委托ClientWorker去获取配置,ClientWorker结合nacos的api去服务端拉取配置。
总结
到了这里大致就分析完seata-config模块了,ConfigurationFactory加载配置文件+spi规定配置的获取,Configuration规定配置项的获取。不同的配置提供者通过对ConfigurationProvider和Configuration的实现来实现配置提供服务,那进一步提一下,如果要nacos支持如:seata.yml这样的从配置中心获取的文件,再从文件中取得配置应该怎么做呢?(说明:实在觉得一项一项的在配置中心的配置太烦琐了),当然其实seata-config-spring-cloud已经支持了在environment中获取配置了,也就是说结合springcloud已经可以在配置中心拿配置文件来获取seata配置了,当然只限于CURRENT_FILE_INSTANCE(严格来说是SpringBootConfigurationProvider提供的支持)。