spring cloud nacos源码笔记(一)如何获取、监听配置

1,145 阅读2分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

基本设置

配置类

@Getter
@Component
@ConfigurationProperties(prefix = "boot")
public class ContextParam {
    boolean taskQueue;

    // 此处打个断点
    public void setTaskQueue(boolean taskQueue) {
        this.taskQueue = taskQueue;
    }
}

bootstrap配置文件

spring.application.name=boot-demo
spring.profiles.active=local
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.service=boot-demo
spring.cloud.nacos.discovery.namespace=1ed08d33-198e-4b84-a65a-4becf4ba0a6f
spring.cloud.nacos.config.namespace=1ed08d33-198e-4b84-a65a-4becf4ba0a6f
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.file-extension=properties

nacos配置文件

image.png

源码分析

此时在属性的set方法中打个断点,修改nacos配置,这时候断点应该会进入,分析调用堆栈 大致上堆栈就是这样的


    rebind:102, ConfigurationPropertiesRebinder 
    .......
    publishEvent:400, AbstractApplicationContext 
    ....
    refresh:65, ContextRefresher (org.springframework.cloud.context.refresh)
    handle:36, RefreshEventListener (org.springframework.cloud.endpoint.event)
    .....
    .....
    receiveConfigInfo:125, NacosContextRefresher$1 (com.alibaba.cloud.nacos.refresh)
    run:190, CacheData$1 (com.alibaba.nacos.client.config.impl)
    safeNotifyListener:211, CacheData (com.alibaba.nacos.client.config.impl)
    checkListenerMd5:161, CacheData (com.alibaba.nacos.client.config.impl)
    run:539, ClientWorker$LongPollingRunnable (com.alibaba.nacos.client.config.impl)
    ....

这里其实已经基本包括了配置的监听、获取所有流程,

从直观能感受到的开始

配置更改

ClientWorker$LongPollingRunnable 这是一个nacos长轮询的线程,ClientWorker内部定义

其run方法大致如下

@Override
public void run() {

    // 缓存数据
    List<CacheData> cacheDatas = new ArrayList<CacheData>();
    List<String> inInitializingCacheList = new ArrayList<String>();
    try {
        .....
        // 检测修改的配置
        List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);

        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 content = getServerConfig(dataId, group, tenant, 3000L);
                CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
                cache.setContent(content);
                ...
            } catch (NacosException ioe) {
                ....
            }
        }
        for (CacheData cacheData : cacheDatas) {
            if (!cacheData.isInitializing() || inInitializingCacheList
                .contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
                // 检测配置md5
                cacheData.checkListenerMd5();
                cacheData.setInitializing(false);
            }
        }


        // 继续启动长轮询
        executorService.execute(this);

    } catch (Throwable e) {

        ....
    }
}

checkListenerMd5 方法如下

void checkListenerMd5() {
    for (ManagerListenerWrap wrap : listeners) {
    // 配置版本(MD5)发生变化通知
        if (!md5.equals(wrap.lastCallMd5)) {
            safeNotifyListener(dataId, group, content, md5, wrap);
        }
    }
}
private void safeNotifyListener(final String dataId, final String group, final String content,
                                final String md5, final ManagerListenerWrap listenerWrap) {
    final Listener listener = listenerWrap.listener;

    Runnable job = new Runnable() {
        @Override
        public void run() {
        。。。。。
            try {
               。。。。。
                ConfigResponse cr = new ConfigResponse();
                cr.setDataId(dataId);
                cr.setGroup(group);
                cr.setContent(content);
                configFilterChainManager.doFilter(null, cr);
                String contentTmp = cr.getContent();
                // 配置修改或者读取回调
                listener.receiveConfigInfo(contentTmp);
                
                // 修改最后版本
                listenerWrap.lastCallMd5 = md5;
               。。
            }
            .....
            finally {
                Thread.currentThread().setContextClassLoader(myClassLoader);
            }
        }
    };


    try {
    // 异步通知
        if (null != listener.getExecutor()) {
            listener.getExecutor().execute(job);
        } else {
            job.run();
        }
    } catch (Throwable t) {
       ....
    }
    
}
// 注册监听器
private void registerNacosListener(final String group, final String dataId) {

   Listener listener = listenerMap.computeIfAbsent(dataId, i -> new Listener() {
      @Override
      public void receiveConfigInfo(String configInfo) {
         refreshCountIncrement();
         String md5 = "";
         if (!StringUtils.isEmpty(configInfo)) {
            try {
            // 最新版本号
               MessageDigest md = MessageDigest.getInstance("MD5");
               md5 = new BigInteger(1, md.digest(configInfo.getBytes("UTF-8")))
                     .toString(16);
            }
            catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
               log.warn("[Nacos] unable to get md5 for dataId: " + dataId, e);
            }
         }
         refreshHistory.add(dataId, md5);
         // 容器刷新事件推送
         // 属性修改
         applicationContext.publishEvent(
               new RefreshEvent(this, null, "Refresh Nacos config"));
         if (log.isDebugEnabled()) {
            log.debug("Refresh Nacos config group " + group + ",dataId" + dataId);
         }
      }

      @Override
      public Executor getExecutor() {
         return null;
      }
   });

   try {
      configService.addListener(dataId, group, listener);
   }
   catch (NacosException e) {
      e.printStackTrace();
   }
}

最终次方法被NacosContextRefresher 调用

@Override
public void onApplicationEvent(ApplicationReadyEvent event) {

   // Spring context 准备完成
   if (this.ready.compareAndSet(false, true)) {
      this.registerNacosListenersForApplications();
   }
}

NacosContextRefresher 这个类呢又是被NacosConfigAutoConfiguration注入进去的

长轮询的注册

com.alibaba.nacos.client.config.impl.ClientWorker#ClientWorker 构造方法如下

public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager, final Properties properties) {
    this.agent = agent;
    this.configFilterChainManager = configFilterChainManager;

    // Initialize the timeout parameter

    init(properties);

    executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
            t.setDaemon(true);
            return t;
        }
    });

    executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
            t.setDaemon(true);
            return t;
        }
    });

    executor.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            try {
                checkConfigInfo();
            } catch (Throwable e) {
                LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
            }
        }
    }, 1L, 10L, TimeUnit.MILLISECONDS);
}

com.alibaba.nacos.client.config.NacosConfigService#NacosConfigService构造方法如下

public NacosConfigService(Properties properties) throws NacosException {
    String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
    if (StringUtils.isBlank(encodeTmp)) {
        encode = Constants.ENCODE;
    } else {
        encode = encodeTmp.trim();
    }
    initNamespace(properties);
    agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
    agent.start();
    worker = new ClientWorker(agent, configFilterChainManager, properties);
}

com.alibaba.cloud.nacos.refresh.NacosContextRefresher#NacosContextRefresher构造方法如下

public NacosContextRefresher(NacosRefreshProperties refreshProperties,
      NacosRefreshHistory refreshHistory, ConfigService configService) {
   this.refreshProperties = refreshProperties;
   this.refreshHistory = refreshHistory;
   this.configService = configService;
}

这样一整套的注册、监听全部都串联起来,核心的类就是 NacosContextRefresher