ClientWorker的checkConfigInfo
先得再说下这个,这个方法是每10
毫秒执行一次,但是并不是会一直去执行新的LongPollingRunnable
任务,是根据监听器的数量决定要不要再启动一个LongPollingRunnable
,每个LongPollingRunnable
默认可以负责3000
个监听器的轮询。所以一般就只是开启了一个,因为是Math.ceil
向上取整,最开始就会开启。
public void checkConfigInfo() {
// 分任务 根据监听器数量开启任务,默认一个任务3000个监听器
int listenerSize = cacheMap.get().size();
// 向上取整为批数
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
这个就是整个轮询获取配置信息的过程,首先会遍历所有的CacheData
,找出是当前任务的加入到一个集合里,这里如果多的话会不会效率比较低啊,可能想办法直接放在任务中比较好吧。然后如果是本次任务的就先检查本地配置,如果有改变的话就要通知监听器。然后去请求服务器获取配置信息,如果是有在初始化的CacheData,那服务器就会立即返回,否则会被挂起,这个原因就是为了不进行频繁的空轮询,又能实现动态配置,只要在挂起的时间段内有改变,就可以理解响应给客户端。获取完之后再检查有没改变,有的话也要通知,然后继续调度当前任务。
@Override
public void run() {
List<CacheData> cacheDatas = new ArrayList<CacheData>();
//是否是在初始化的CacheData,会影响服务器是否挂起或者立即返回
List<String> inInitializingCacheList = new ArrayList<String>();
try {
// check failover config
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);
}
}
}
//获取有变化的配置列表dataid+group,访问的url是/listener
// check server config
List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
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]);//设置配置类型
}
...
} catch (NacosException ioe) {
...
}
}
for (CacheData cacheData : cacheDatas) {//不是初始化中的或者初始化集合里存在的
if (!cacheData.isInitializing() || inInitializingCacheList
.contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
cacheData.checkListenerMd5();//检查是否有变化,有变化就通知
cacheData.setInitializing(false);//请求过了后就设置为不在初始化中,这样就会被挂起,如果服务器配置有更新,就会立即返回,这样就可以实现动态配置更新,又不会太多的空轮询消耗
}
}
inInitializingCacheList.clear();
executorService.execute(this);
} catch (Throwable e) {
LOGGER.error("longPolling error : ", e);
executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
}
}
}
checkLocalConfig检查本地配置
如果不没用本地文件,但是文件存在或者如果用本地文件,本地文件并有更新,就获取内容,设置版本,设置setUseLocalConfigInfo(true)
通知标记返回。如果用本地文件,但是不存在,不用设置通知,直接返回。
//检查配置
private void checkLocalConfig(CacheData cacheData) {
final String dataId = cacheData.dataId;
final String group = cacheData.group;
final String tenant = cacheData.tenant;
File path = LocalConfigInfoProcessor.getFailoverFile(agent.getName(), dataId, group, tenant);
// 不用本地配置,但是本地配置存在,获取本地的
// 没有 -> 有
if (!cacheData.isUseLocalConfigInfo() && path.exists()) {
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
String md5 = MD5.getInstance().getMD5String(content);
cacheData.setUseLocalConfigInfo(true);//设置用本地的
cacheData.setLocalConfigInfoVersion(path.lastModified());//设置版本
cacheData.setContent(content);
LOGGER.warn("[{}] [failover-change] failover file created. dataId={}, group={}, tenant={}, md5={}, content={}",
agent.getName(), dataId, group, tenant, md5, ContentUtils.truncateContent(content));
return;
}
// 用本地配置文件,但是不存在,直接返回
// 有 -> 没有。不通知业务监听器,从server拿到配置后通知。
if (cacheData.isUseLocalConfigInfo() && !path.exists()) {
cacheData.setUseLocalConfigInfo(false);
LOGGER.warn("[{}] [failover-change] failover file deleted. dataId={}, group={}, tenant={}", agent.getName(),
dataId, group, tenant);
return;
}
// 用本地,存在,版本有变更,更新
// 有变更
if (cacheData.isUseLocalConfigInfo() && path.exists()
&& cacheData.getLocalConfigInfoVersion() != path.lastModified()) {
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
String md5 = MD5.getInstance().getMD5String(content);
cacheData.setUseLocalConfigInfo(true);
cacheData.setLocalConfigInfoVersion(path.lastModified());
cacheData.setContent(content);
LOGGER.warn("[{}] [failover-change] failover file changed. dataId={}, group={}, tenant={}, md5={}, content={}",
agent.getName(), dataId, group, tenant, md5, ContentUtils.truncateContent(content));
}
}
checkUpdateDataIds获取有改变的配置文件
这个是把所有的CacheData
的相关信息都连起来,一次性批量请求。但是其中有个比较重要的就是inInitializingCacheList
,这个表示里面是否有正在初始化的CacheData
,如果有的话后面会设置一个标记,是的服务器不会挂起请求,会立即响应。 这里的响应只是告诉你哪些是有变化的,不会把内容给你,后面还得请求内容。
List<String> checkUpdateDataIds(List<CacheData> cacheDatas, List<String> inInitializingCacheList) throws IOException {
StringBuilder sb = new StringBuilder();//把配置信息都连起来,一次请求
for (CacheData cacheData : cacheDatas) {
if (!cacheData.isUseLocalConfigInfo()) {//不用本地的
sb.append(cacheData.dataId).append(WORD_SEPARATOR);
sb.append(cacheData.group).append(WORD_SEPARATOR);
if (StringUtils.isBlank(cacheData.tenant)) {
sb.append(cacheData.getMd5()).append(LINE_SEPARATOR);
} else {
sb.append(cacheData.getMd5()).append(WORD_SEPARATOR);
sb.append(cacheData.getTenant()).append(LINE_SEPARATOR);
}
if (cacheData.isInitializing()) {
// cacheData 首次出现在cacheMap中&首次check更新
inInitializingCacheList
.add(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant));
}
}
}
boolean isInitializingCacheList = !inInitializingCacheList.isEmpty();//是否是初始化的获取标记
return checkUpdateConfigStr(sb.toString(), isInitializingCacheList);
}
checkUpdateDataIds服务器获取
这里就会根据isInitializingCacheList
来设置一个标记,让服务器判断是否要挂起,请求的url
是/v1/cs/configs/listener
,这里超时增加了,默认变成了45秒,就是为了应对挂起和检查配置文件变更。内部怎么请求的前面讲过了,就不多说了。这里根据情况还要设置服务器健康状态setHealthServer
,然后拿到改变的配置文件结果解析后返回。
List<String> checkUpdateConfigStr(String probeUpdateString, boolean isInitializingCacheList) throws IOException {
List<String> params = new ArrayList<String>(2);
params.add(Constants.PROBE_MODIFY_REQUEST);
params.add(probeUpdateString);
List<String> headers = new ArrayList<String>(2);
headers.add("Long-Pulling-Timeout");
headers.add("" + timeout);
if (isInitializingCacheList) {//是初始化的会设置一个请求头标记
headers.add("Long-Pulling-Timeout-No-Hangup");
headers.add("true");
}
if (StringUtils.isBlank(probeUpdateString)) {
return Collections.emptyList();
}
try {
// 增加超时时间,防止被挂起,只有初始化的时候isInitializingCacheList=true不会挂起,应该是服务器看了请求头Long-Pulling-Timeout-No-Hangup才不会挂起
long readTimeoutMs = timeout + (long) Math.round(timeout >> 1);//45秒
HttpResult result = agent.httpPost(Constants.CONFIG_CONTROLLER_PATH + "/listener", headers, params,
agent.getEncode(), readTimeoutMs);
if (HttpURLConnection.HTTP_OK == result.code) {
setHealthServer(true);
return parseUpdateDataIdResponse(result.content);
} else {
setHealthServer(false);
LOGGER.error("[{}] [check-update] get changed dataId error, code: {}", agent.getName(), result.code);
}
} catch (IOException e) {
setHealthServer(false);
LOGGER.error("[" + agent.getName() + "] [check-update] get changed dataId exception", e);
throw e;
}
return Collections.emptyList();
}
下篇继续。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。