Soul 网关源码学习(9) - HTTP 长轮询数据同步机制 (2)

前一篇文章中描述了 HTTP 长轮询的原理以及在 soul admin 端提供的接口支持 HTTP 轮询的接口的处理流程, 这一篇将从 soul 网关中 HTTP 长轮询客户端的处理流程来分析客户端侧是如何完成长轮询处理流程的。soul 网关中提供长轮询客户端处理功能的模块是
soul-sync-data-http

soul-sync-data-http 模块源代码分析

org.dromara.soul.sync.data.http.config.HttpConfig 的源代码如下:

@Data
public class HttpConfig {

private String url;

private Integer delayTime;

private Integer connectionTimeout;
}
  • url 属性提供长轮询服务端的地址, 可配置多个地址, 多个地址以半角逗号(,)分隔
  • delayTime 目前的版本有配置入口, 但是在代码中未看到有使用的地方
  • connectionTimeout 目前的版本有配置入口, 但是在代码中未看到有使用的地方

soul-sync-data-http 模块内各个类之间的类图:

img.png

和 soul admin 提供的场论询接口通讯的功能在类 HttpSyncDataService, soul bootstrap 如果选择使用 HTTP 长轮询的方式来同步配置信息的话, HttpSyncDataService 保证 soul bootstrap 能从 soul admin 获取到配置信息的机制为:

  1. HttpSyncDataService 的构造信息中获取配置信息
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();
}

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

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

private void doFetchGroupConfig(final String server, final ConfigGroupEnum... groups) {
StringBuilder params = new StringBuilder();
for (ConfigGroupEnum groupKey : groups) {
params.append("groupKeys").append("=").append(groupKey.name()).append("&");
}
String url = server + "/configs/fetch?" + StringUtils.removeEnd(params.toString(), "&");
log.info("request configs: [{}]", url);
String json = null;
try {
json = this.httpClient.getForObject(url, String.class);
} catch (RestClientException e) {
String message = String.format("fetch config fail from server[%s], %s", url, e.getMessage());
log.warn(message);
throw new SoulException(message, e);
}
// update local cache
boolean updated = this.updateCacheWithJson(json);
if (updated) {
log.info("get latest configs: [{}]", json);
return;
}
// not updated. it is likely that the current config server has not been updated yet. wait a moment.
log.info("The config of the server[{}] has not been updated or is out of date. Wait for 30s to listen for changes again.", server);
ThreadUtils.sleep(TimeUnit.SECONDS, 30);
}
  1. HttpLongPollingTask 中的轮询, 轮询的实现方法在 org.dromara.soul.sync.data.http.HttpSyncDataService.HttpLongPollingTask#runorg.dromara.soul.sync.data.http.HttpSyncDataService#doLongPolling

上述的两种场景中如果从 soul admin 测获取到 ConfigGroup 的配置发生了变化, 就会调用 org.dromara.soul.sync.data.http.HttpSyncDataService#fetchGroupConfig 方法调用获取配置的接口, 获取最新的网关配置信息, 将配置信息通过各个 subscriber
同步更新到 soul bootstrap.

总结

至此, soul admin 和 soul bootstrap 之间使用 HTTP 长轮询同步数据已经学习完成, 整个流程的设计条理清晰。通过这部分源码的学习,对 HTTP 服务端和 HTTP 客户端的通讯机制有了进一步的了解,也掌握了一个具体落实 HTTP 长轮询的方式,希望在今后的编程实践中能够有机会使用到。

文章作者: David Liu
文章链接: https://davidliu.now.sh/2021/01/23/soul_dependency_discoveryIV/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 David Liu's Blog