环境
今天讨论的版本是2.2.1 - release版本;
soul-admin 和 soul-bootstrap都集群部署
分析
问题
admin在做集群不是的时候,如果用户修改配置,当节点数据只到达其中一个而没有到达其他节点时,其他节点会有一个5分钟的延时。
admin节点和节点之间时互不感知的。
在HttpLongPollingDataChangedListenner 的afterInitialize
方法中会通过一个5分钟的定时任务去拉取数据到admin缓存中。
@Override
protected void afterInitialize() {
long syncInterval = httpSyncProperties.getRefreshInterval().toMillis();
// Periodically check the data for changes and update the cache
scheduler.scheduleWithFixedDelay(() -> {
log.info("http sync strategy refresh config start.");
try {
this.refreshLocalCache();
log.info("http sync strategy refresh config success.");
} catch (Exception e) {
log.error("http sync strategy refresh config error!", e);
}
}, syncInterval, syncInterval, TimeUnit.MILLISECONDS);
log.info("http sync strategy refresh interval: {}ms", syncInterval);
}
处理方式
admin,提供2个接口,一个是listener,一个是fetch
1、listener接口,用于hold来自web的http请求,如果没有配置变更,会阻塞http请求,直到超时;如果有配置变更,会响应请求,告诉web是哪个group的配置发生了变更;
2、fetch接口,用于拉取group配置组的数据;
如果有多个Admin, 例如a, b, c三个节点,更新的操作只到达了一个节点;web会监听所有的admin节点,一旦节点有变化,就会收到最新的配置数据,但是某些节点数据,并不是最新的,这样倒置bootstrap拉不到最新的数据,所以bootstrap请求的时候会带上last_update_time 来监听listener,这个时候admin会对比时间戳,如果时间落后了,则更新本地配置数据;
private boolean checkCacheDelayAndUpdate(final ConfigDataCache serverCache, final String clientMd5, final long clientModifyTime) {
...
long lastModifyTime = serverCache.getLastModifyTime();
if (lastModifyTime >= clientModifyTime) {
// 客户端配置过时了
return true;
}
// the lastModifyTime before client, then the local cache needs to be updated.
// Considering the concurrency problem, admin must lock,
// otherwise it may cause the request from soul-web to update the cache concurrently, causing excessive db pressure
...
if (locked) {
try {
ConfigDataCache latest = CACHE.get(serverCache.getGroup());
if (latest != serverCache) {
// the cache of admin was updated. if the md5 value is the same, there's no need to update.
return !StringUtils.equals(clientMd5, latest.getMd5());
}
// 从数据库加载配置到缓存中
this.refreshLocalCache();
latest = CACHE.get(serverCache.getGroup());
return !StringUtils.equals(clientMd5, latest.getMd5());
} finally {
LOCK.unlock();
}
}
...
}