在上篇文章spring cloud自动刷新配置信息中,写到spring cloud刷新配置是通过添加listener来监听配置变化的。这篇文章将详细介绍nacos客户端的监听机制。
nacos添加监听listener是通过ClientWorker的addTenantListeners方法来实现的,会将需要监听的数据都缓存到内存的map中,key就是dataId,group,namespace拼接起来的字符串,value是CacheData对象,CacheData类里属性主要有listener数组,配置内容content,配置内容的md5摘要等。addTenantListeners方法实现代码如下:
public void addTenantListeners(String dataId, String group, List extends Listener> listeners)
throws NacosException {
group = null2defaultGroup(group);
String tenant = agent.getTenant();
//缓存需要监听的配置信息
CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);
for (Listener listener : listeners) {
//添加listener到监听列表中
cache.addListener(listener);
}
}
监听配置变化的逻辑也是在ClientWorker类实现的,在创建ClientWorker对象时,会自动启动一个定时任务,定时去检查是否有新增的要监听配置:
this.executor.scheduleWithFixedDelay(new Runnable() {
//每10秒执行一次定时任务
@Override
public void run() {
try {
checkConfigInfo();
} catch (Throwable e) {
LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
}
}
}, 1L, 10L, TimeUnit.MILLISECONDS);
checkConfigInfo是检查配置变化的核心方法,会把缓存的数据默认分成每3000条一组,通过线程池去异步执行,实际上监听的数据应该都不会达到此数量级。后续此定时任务只用于检查是否新增监听配置超过了3000条,如果超过了,会再提交一组任务。
public void checkConfigInfo() {
// Dispatch taskes.
int listenerSize = cacheMap.get().size();
//每3000个分组数据分成一组执行,实际上应该都不会有那么多
int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
if (longingTaskCount > currentLongingTaskCount) {
for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
//线程池异步执行
executorService.execute(new LongPollingRunnable(i));
}
currentLongingTaskCount = longingTaskCount;
}
}
LongPollingRunnable的run方法核心逻辑如下:如果启动了本地failover机制,会优先去本地查询配置是否变化,判断依据就是数据的md5值是否有变化。之后会调用nacos服务端接口,查询有哪些配置信息发生了变化,对于发生变化的配置信息,会再获取配置信息详情,通过listener回调方法刷新数据。最后,如果没有发生异常,会再把此任务提交到线程池循环执行;如果有异常,会停留一段时间再执行。
@Override
public void run() {
List cacheDatas = new ArrayList();
List inInitializingCacheList = new ArrayList();
try {
// 检查本地配置
for (CacheData cacheData : cacheMap.get().values()) {
if (cacheData.getTaskId() == taskId) {
cacheDatas.add(cacheData);
try {
checkLocalConfig(cacheData);
if (cacheData.isUseLocalConfigInfo()) {
cacheData.checkListenerMd5();
}
} catch (Exception e) {
LOGGER.error("get local config info error", e);
}
}
}
//调用服务端接口,查询有哪些配置发生了变化
List changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
if (!CollectionUtils.isEmpty(changedGroupKeys)) {
LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
}
for (String groupKey : changedGroupKeys) {
String[] key = GroupKey.parseKey(groupKey);
String dataId = key[0];
String group = key[1];
String tenant = null;
if (key.length == 3) {
tenant = key[2];
}
try {
//查询发生变化的配置详情
String[] ct = getServerConfig(dataId, group, tenant, 3000L);
CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
cache.setContent(ct[0]);
if (null != ct[1]) {
cache.setType(ct[1]);
}
LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}",
agent.getName(), dataId, group, tenant, cache.getMd5(),
ContentUtils.truncateContent(ct[0]), ct[1]);
} catch (NacosException ioe) {
String message = String
.format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",
agent.getName(), dataId, group, tenant);
LOGGER.error(message, ioe);
}
}
for (CacheData cacheData : cacheDatas) {
if (!cacheData.isInitializing() || inInitializingCacheList
.contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
//再次检查md5值,发生变化会通知listener
cacheData.checkListenerMd5();
cacheData.setInitializing(false);
}
}
inInitializingCacheList.clear();
//没有异常,会循环接着执行
executorService.execute(this);
} catch (Throwable e) {
// 如果发生了异常,会过一段再执行
LOGGER.error("longPolling error : ", e);
executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
}
}
CacheData的checkListenerMd5方法会匹配md5值,如果md5值发生了变化,会通知listener列表
void checkListenerMd5() {
for (ManagerListenerWrap wrap : listeners) {
if (!md5.equals(wrap.lastCallMd5)) {
//通知listener
safeNotifyListener(dataId, group, content, type, md5, wrap);
}
}
}