本文主要講解 bootstrap 端啟動后數(shù)據(jù)的同步和更新時的同步流程淘讥。上文講解到 admin 端啟動時主要的幾個步驟,那么 bootstrap 端再啟動后泻拦,admin 端又做了哪些操作呢毙芜?
啟動 bootstrap
從上文的日志文件中,可以看到 bootstrap 啟動時做了幾件事情:
- 配置了 http long pull 的同步方式
- 請求配置: requst config
- 清除所有緩存:
clear all XX cache
- 獲取最新的配置信息
代碼出發(fā)
第一步映入眼簾的便是 : HttpSyncDataConfiguration, 在該 方法中争拐,注冊了 HttpSyncDataService 的 Bean腋粥。 進(jìn)入到 HttpSyncDataService 的構(gòu)造方法中, 調(diào)用了 start() 方法
private void start() {
// It could be initialized multiple times, so you need to control that.
// 這里有個疑問:為什么會有 cas 的操作, start 直接注冊到 bean 。
if (RUNNING.compareAndSet(false, true)) {
// 同步所有設(shè)置
this.fetchGroupConfig(ConfigGroupEnum.values());
// 下面都是申請資源
...
} else {
log.info("soul http long polling was started, executor=[{}]", executor);
}
}
進(jìn)入 fetchGroup 中隘冲,核心的方法 doFetchGroupConfig闹瞧, 將所有的配置組合成一個參數(shù)傳遞到 Admin 端,請求接口為
"/configs/fetch?" 展辞,進(jìn)入 admin 跟蹤這個鏈路奥邮。
在 ConfigController#fetchConfigs 方法中,映射了 "/configs/fetch" 這個路徑纵竖, 跟蹤到 fetchConfig 時漠烧,主要是從之前 admin 端啟動后緩存到內(nèi)存中的數(shù)據(jù)。
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 中靡砌,繼續(xù)往下走, 執(zhí)行到: this.executor.execute(new HttpLongPollingTask(server)));
中時已脓,會有長輪詢的任務(wù)開始執(zhí)行。進(jìn)入 run 中通殃,主要方式是 doLongPolling(), 進(jìn)入 doLongPolling() 度液。 有發(fā)送 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":[]}
這時又得進(jìn)入 admin 端一探究竟堕担。進(jìn)入 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);
便能得到以下日志:
看下時間有數(shù)據(jù)立馬返回齐疙,沒有數(shù)據(jù)就1分鐘后返回。這個是循環(huán)請求的旭咽。 請求完成后如果有變更贞奋,則進(jìn)入 doFetchGroupConfig, 會通過
/configs/fetch
再次請求變更的內(nèi)容穷绵。
總結(jié)
- 正常流程已經(jīng)斷斷續(xù)續(xù)的更完了轿塔,那異常流程還沒有。正常流程的流程圖:
- 多個 admin 實(shí)例的情況該怎么做仲墨。接下來會繼續(xù)更新勾缭。