简介
上一篇已经分析了websocket方式数据同步的整理流程,这一篇来分析一下http方式同步数据到soul网关是如何处理的。
示例运行
涉及修改的配置如下:
soul-Admin端:
sync:
http:
enabled: true
soul-Bootstrap端:
soul :
file:
enabled: true
corss:
enabled: true
dubbo :
parameter: multi
sync:
http:
url : http://localhost:9095
分别启动Soul-admin、Soul-Bootstrap、Soul-Example-HTTP后进入源码分析
源码分析
通过Soul网关源码分析-websocket数据同步的分析,我们知道数据同步的核心类是CommonPluginDataSubscriber,这个类是用于订阅消息变更的,那么我们就从这个类入手,一步步将Http方式数据同步的流程梳理出来,我们在插件数据更新方法中打个断点,代码片段如下:
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
@Override
public void onSubscribe(final PluginData pluginData) {
subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
}
}
通过alt+F7查看方法调用栈,发现是PluginDataRefresh的refresh方法调用到此方法,代码片段如下:
public class PluginDataRefresh extends AbstractDataRefresh<PluginData> {
@Override
protected void refresh(final List<PluginData> data) {
if (CollectionUtils.isEmpty(data)) {
log.info("clear all plugin data cache");
pluginDataSubscriber.refreshPluginDataAll();
} else {
pluginDataSubscriber.refreshPluginDataAll();
data.forEach(pluginDataSubscriber::onSubscribe);
}
}
}
继续跟踪方法调用栈,发现AbstractDataRefresh这个类的refresh方法调用到PluginDataRefresh的refresh方法,关键代码如下:
public abstract class AbstractDataRefresh<T> implements DataRefresh {
@Override
public Boolean refresh(final JsonObject data) {
boolean updated = false;
JsonObject jsonObject = convert(data);
if (null != jsonObject) {
ConfigData<T> result = fromJson(jsonObject);
if (this.updateCacheIfNeed(result)) {
updated = true;
refresh(result.getData());
}
}
return updated;
}
}
继续跟踪方法调用栈,发现是DataRefreshFactory的executor方法调用了AbstractDataRefresh这个类的refresh方法,而且这里看起来是使用了工厂模式,根据不同的数据类型,生产对应的DataRefresh来更新数据,关键代码如下:
public final class DataRefreshFactory {
private static final EnumMap<ConfigGroupEnum, DataRefresh> ENUM_MAP = new EnumMap<>(ConfigGroupEnum.class);
/**
* Instantiates a new Data refresh factory.
*
* @param pluginDataSubscriber the plugin data subscriber
* @param metaDataSubscribers the meta data subscribers
* @param authDataSubscribers the auth data subscribers
*/
public DataRefreshFactory(final PluginDataSubscriber pluginDataSubscriber,
final List<MetaDataSubscriber> metaDataSubscribers,
final List<AuthDataSubscriber> authDataSubscribers) {
ENUM_MAP.put(ConfigGroupEnum.PLUGIN, new PluginDataRefresh(pluginDataSubscriber));
ENUM_MAP.put(ConfigGroupEnum.SELECTOR, new SelectorDataRefresh(pluginDataSubscriber));
ENUM_MAP.put(ConfigGroupEnum.RULE, new RuleDataRefresh(pluginDataSubscriber));
ENUM_MAP.put(ConfigGroupEnum.APP_AUTH, new AppAuthDataRefresh(authDataSubscribers));
ENUM_MAP.put(ConfigGroupEnum.META_DATA, new MetaDataRefresh(metaDataSubscribers));
}
/**
* Executor.
*
* @param data the data
* @return the boolean
*/
public boolean executor(final JsonObject data) {
final boolean[] success = {false};
ENUM_MAP.values().parallelStream().forEach(dataRefresh -> success[0] = dataRefresh.refresh(data));
return success[0];
}
}
继续跟踪方法调用栈,发现是HttpSyncDataService类的updateCacheWithJson方法调用了DataRefreshFactory的executor方法,关键代码如下:
public class HttpSyncDataService implements SyncDataService, AutoCloseable {
private boolean updateCacheWithJson(final String json) {
JsonObject jsonObject = GSON.fromJson(json, JsonObject.class);
JsonObject data = jsonObject.getAsJsonObject("data");
// if the config cache will be updated?
return factory.executor(data);
}
}
继续跟踪方法栈,发现它自身的doFetchGroupConfig方法调用了updateCacheWithJson方法,如下:
private void doFetchGroupConfig(final String server, final ConfigGroupEnum... groups) {
...
// update local cache
boolean updated = this.updateCacheWithJson(json);
if (updated) {
log.info("get latest configs: [{}]", json);
return;
}
...
}
继续跟踪,发现还是自身的fetchGroupConfig方法对doFetchGroupConfig进行了调用,如下;
private void fetchGroupConfig(final ConfigGroupEnum... groups) throws SoulException {
for (int index = 0; index < this.serverList.size(); index++) {
String server = serverList.get(index);
try {
this.doFetchGroupConfig(server, groups);
break;
} catch (SoulException e) {
// no available server, throw exception.
if (index >= serverList.size() - 1) {
throw e;
}
log.warn("fetch config fail, try another one: {}", serverList.get(index + 1));
}
}
}
继续跟,发现是自身的start方法调用了fetchGroupConfig方法,如下:
private void start() {
// It could be initialized multiple times, so you need to control that.
if (RUNNING.compareAndSet(false, true)) {
// fetch all group configs.
this.fetchGroupConfig(ConfigGroupEnum.values());
int threadSize = serverList.size();
this.executor = new ThreadPoolExecutor(threadSize, threadSize, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
SoulThreadFactory.create("http-long-polling", true));
// start long polling, each server creates a thread to listen for changes.
this.serverList.forEach(server -> this.executor.execute(new HttpLongPollingTask(server)));
} else {
log.info("soul http long polling was started, executor=[{}]", executor);
}
}
接着往下跟,发现自身的构造函数被执行时就会执行start方法,如下:
public HttpSyncDataService(final HttpConfig httpConfig, final PluginDataSubscriber pluginDataSubscriber,
final List<MetaDataSubscriber> metaDataSubscribers, final List<AuthDataSubscriber> authDataSubscribers) {
this.factory = new DataRefreshFactory(pluginDataSubscriber, metaDataSubscribers, authDataSubscribers);
this.httpConfig = httpConfig;
this.serverList = Lists.newArrayList(Splitter.on(",").split(httpConfig.getUrl()));
this.httpClient = createRestTemplate();
this.start();
}
最后发现是HttpSyncDataConfiguration这个类在自动装载时,会调用HttpSyncDataService的构造函数创建HttpSyncDataService实例对象,如下;
@Configuration
@ConditionalOnClass(HttpSyncDataService.class)
@ConditionalOnProperty(prefix = "soul.sync.http", name = "url")
@Slf4j
public class HttpSyncDataConfiguration {
@Bean
public SyncDataService httpSyncDataService(final ObjectProvider<HttpConfig> httpConfig, final ObjectProvider<PluginDataSubscriber> pluginSubscriber,
final ObjectProvider<List<MetaDataSubscriber>> metaSubscribers, final ObjectProvider<List<AuthDataSubscriber>> authSubscribers) {
log.info("you use http long pull sync soul data");
return new HttpSyncDataService(Objects.requireNonNull(httpConfig.getIfAvailable()), Objects.requireNonNull(pluginSubscriber.getIfAvailable()),
metaSubscribers.getIfAvailable(Collections::emptyList), authSubscribers.getIfAvailable(Collections::emptyList));
}
}
总结
经过上述分析,http数据同步的大体流程如下图所示:
下一篇再根据这个大体流程深究里面的细节。