本文主要讲解 bootstrap 端启动后数据的同步和更新时的同步流程。上文讲解到 admin 端启动时主要的几个步骤,那么 bootstrap 端再启动后,admin 端又做了哪些操作呢?
启动 bootstrap
从上文的日志文件中,可以看到 bootstrap 启动时做了几件事情:
- 配置了 http long pull 的同步方式
- 请求配置: requst config
- 清除所有缓存:
clear all XX cache
- 获取最新的配置信息
代码出发
第一步映入眼帘的便是 : HttpSyncDataConfiguration, 在该 方法中,注册了 HttpSyncDataService 的 Bean。 进入到 HttpSyncDataService 的构造方法中, 调用了 start() 方法
private void start() {
// It could be initialized multiple times, so you need to control that.
// 这里有个疑问:为什么会有 cas 的操作, start 直接注册到 bean 。
if (RUNNING.compareAndSet(false, true)) {
// 同步所有设置
this.fetchGroupConfig(ConfigGroupEnum.values());
// 下面都是申请资源
...
} else {
log.info("soul http long polling was started, executor=[{}]", executor);
}
}
进入 fetchGroup 中,核心的方法 doFetchGroupConfig, 將所有的配置組合成一个参数传递到 Admin 端,请求接口为
"/configs/fetch?" ,进入 admin 跟踪这个链路。
在 ConfigController#fetchConfigs 方法中,映射了 "/configs/fetch" 这个路径, 跟踪到 fetchConfig 时,主要是从之前 admin 端启动后缓存到内存中的数据。
public ConfigData<?> fetchConfig(final ConfigGroupEnum groupKey) {
ConfigDataCache config = CACHE.get(groupKey.name());
switch (groupKey) {
case APP_AUTH:
List<AppAuthData> appAuthList = GsonUtils.getGson().fromJson(config.getJson(), new TypeToken<List<AppAuthData>>() {
}.getType());
return new ConfigData<>(config.getMd5(), config.getLastModifyTime(), appAuthList);
}
...
}
回到 bootstrap 中,继续往下走, 执行到: this.executor.execute(new HttpLongPollingTask(server)));
中时,会有长轮询的任务开始执行。进入 run 中,主要方式是 doLongPolling(), 进入 doLongPolling() 。 有发送 Post 的 ”/configs/listener", 但是这是并没有立即返回,而是在差不多一分钟左右返回。
2021-01-30 07:14:31.876 INFO 43588 --- [-long-polling-1] o.d.s.s.data.http.HttpSyncDataService : request listener configs: [http://localhost:9095/configs/listener]
...
2021-01-30 07:15:40.015 INFO 43588 --- [-long-polling-1] o.d.s.s.data.http.HttpSyncDataService : listener result: [{"code":200,"message":"success","data":[]}
这时又得进入 admin 端一探究竟。进入 HttpLongPollingDataChangedListener 中, doLongPolling 为主要核心逻辑。
public void doLongPolling(final HttpServletRequest request, final HttpServletResponse response) {
// 比较 md5 是否一致
List<ConfigGroupEnum> changedGroup = compareChangedGroup(request);
String clientIp = getRemoteIp(request);
// 有变化立即返回
if (CollectionUtils.isNotEmpty(changedGroup)) {
this.generateResponse(response, changedGroup);
log.info("send response with the changed group, ip={}, group={}", clientIp, changedGroup);
return;
}
// 启动异步返回
final AsyncContext asyncContext = request.startAsync();
// AsyncContext.settimeout() does not timeout properly, so you have to control it yourself
asyncContext.setTimeout(0L);
// SERVER_MAX_HOLD_TIMEOUT = 60s, 60s 后返回。
scheduler.execute(new LongPollingClient(asyncContext, clientIp, HttpConstants.SERVER_MAX_HOLD_TIMEOUT));
}
class LongPollingClient implements Runnable {
...
@Override
public void run() {
this.asyncTimeoutFuture = scheduler.schedule(() -> {
clients.remove(LongPollingClient.this);
List<ConfigGroupEnum> changedGroups = compareChangedGroup((HttpServletRequest) asyncContext.getRequest());
sendResponse(changedGroups);
}, timeoutTime, TimeUnit.MILLISECONDS);
clients.add(this);
}
...
}
接着又回到 bootstrap 端,这里需要将 HttpSyncDataService#doLongPolling 中日志 debug 更改成 info,
log.debug("request listener configs: [{}]", listenerUrl) => log.info("request listener configs: [{}]", listenerUrl);
log.debug("listener result: [{}]", json) => blog.info("listener result: [{}]", json);
便能得到以下日志:
看下时间有数据立马返回,没有数据就1分钟后返回。这个是循环请求的。 请求完成后如果有变更,则进入 doFetchGroupConfig, 会通过
/configs/fetch
再次请求变更的内容。
总结
- 正常流程已经断断续续的更完了,那异常流程还没有。正常流程的流程图:
- 多个 admin 实例的情况该怎么做。接下来会继续更新。