1:nacos交互模型

nacos配置心的交互模型是pull模型,应用长轮询的方式来获取配置数据。

2:长轮询

由服务端控制响应客户端请求的返回时间,减少客户端无效请求。
客户端发起请求后,服务端不会立即返回结果,而是将请求挂起一段时间,如果此段时间内服务端数据变更,则立即响应客户端请求,如果一直无变化则等到超时时间后响应请求,客户端再重新发起长连接。
长轮询.png

3:nacos架构简述

nacos拉取配置流程.png

4:源码分析

基于nacos 1.4.2 版本分析
先了解一下nacos在客户端的几个对象

  1. //ClientWorker类属性
  2. //key是groupKey,由dataId,group,tenant拼接的字符串
  3. //value是cacheData对象,每个dataId都会持有一个cacheData对象
  4. private final ConcurrentHashMap<String, CacheData> cacheMap = new ConcurrentHashMap();

1:ConfigFactory工厂类

ConfigFactory.bmp
此工厂类作用即初始化NacosConfigService

2:NacosConfigService

NacosConfigService.bmp
重点关注其中两个方法 getConfig(…) 和 getConfigAndSignListener(…) ;其中getconfig方法只是发送普通的http请求获取配置信息,而getConfigAndSignListener方法则多了addTenantListenersWithContent(…)方法,此方法主要作用是发起长轮询和对dataId数据变更注册监听的操作;进入方法看一下
addTenantListenersWithContent.bmp
方法很简单,我们主要观察addCacheDataIfAbsent(…)做了什么事情,点进去方法
addCacheDataIfAbsent.bmp
可以看到,先从本地缓存cacheMap中取值CacheData,如果取不到则向服务端发起长轮询请求获取配置,默认超时时间30s,并把返回数据填充到cacheMap中key对应CacheData对象。在addTenantListenersWithContent(…)方法中注册监听器。
接下来看一下CacheData对象
CacheData.bmp
重点关注listeners属性和addListener(…),checkListenerMd5()方法,还有静态内部类ManagerListenerWrap。属性md5是content真实配置数据计算出来的md5值。

  1. public void addListener(Listener listener) {
  2. if (null == listener) {
  3. throw new IllegalArgumentException("listener is null");
  4. } else {
  5. // 添加监听时会把最新的md5值赋值给ManagerListenerWrap的lastCallMd5属性
  6. CacheData.ManagerListenerWrap wrap = listener instanceof AbstractConfigChangeListener ? new CacheData.ManagerListenerWrap(listener, this.md5, this.content) : new CacheData.ManagerListenerWrap(listener, this.md5);
  7. if (this.listeners.addIfAbsent(wrap)) {
  8. LOGGER.info("[{}] [add-listener] ok, tenant={}, dataId={}, group={}, cnt={}", new Object[]{this.name, this.tenant, this.dataId, this.group, this.listeners.size()});
  9. }
  10. }
  11. }
  12. void checkListenerMd5() {
  13. Iterator var1 = this.listeners.iterator();
  14. while(var1.hasNext()) {
  15. CacheData.ManagerListenerWrap wrap = (CacheData.ManagerListenerWrap)var1.next();
  16. //对比当前md5值和之前md5值是否一致,不一致则执行safeNotifyListener方法异步更新对应配置
  17. if (!this.md5.equals(wrap.lastCallMd5)) {
  18. this.safeNotifyListener(this.dataId, this.group, this.content, this.type, this.md5, this.encryptedDataKey, wrap);
  19. }
  20. }
  21. }
  22. private static class ManagerListenerWrap {
  23. //监听器
  24. final Listener listener;
  25. //lastMD5值
  26. String lastCallMd5 = CacheData.getMd5String((String)null);
  27. //last数据
  28. String lastContent = null;
  29. ManagerListenerWrap(Listener listener) {
  30. this.listener = listener;
  31. }
  32. ManagerListenerWrap(Listener listener, String md5) {
  33. this.listener = listener;
  34. this.lastCallMd5 = md5;
  35. }
  36. ManagerListenerWrap(Listener listener, String md5, String lastContent) {
  37. this.listener = listener;
  38. this.lastCallMd5 = md5;
  39. this.lastContent = lastContent;
  40. }
  41. }

再看NacosConfigService初始化时会初始化属性ClientWorker,ClientWorker在init时加载超时配置,再就是初始化了两个线程池
ClientWorker.bmp
两个线程初始化之后,executer只有一个线程,用来定时执行checkConfigInfo(…)方法,我么可以看出来是在1s后执行,每10s执行一次;
checkConfigInfo.bmp
longingTaskCount是缓存cacheMap的长度/3000,currentLongingTaskCount初始值是0,当longingTaskCount>currentLongingTaskCount时,拿currentLongingTaskCount做起始数做循环执行ClientWorker.LongPollingRunnable方法。LongPollingRunnable.jpg
而此方法中执行了checkLocalConfig(CacheData cacheData)方法。这个方法应该是更新本地缓存文件的方法,通过检查文件状态和cacheData的isUseLocalConfigInfo属性和LocalConfigInfoVersion版本号对比确定要不要更新本地缓存配置文件。
checkLocalConfig.jpg
checkListenerMd5方法检查MD5的值是否一致,不一致则表示数据变更,触发safeNotifyListener(…)方法,此方法单独开线程,向所有对dataId注册过监听的客户端推送变更后的数据内容。客户端接收到通知通过receiveConfigInfo(…)方法接受回调数据。
checkListenerMd5.jpg