nacos 源码阅读(二)-配置中心配置变动流程

532 阅读4分钟

1. 概述

Nacos的配置变动处理流程大致包括以下几个步骤:

  1. 客户端监听配置:客户端通过Nacos的SDK或API监听特定的配置。
  2. 服务端接收监听请求:Nacos服务端接收客户端的监听请求,并维护监听关系。
  3. 配置变更发布:当配置发生变更时(如通过Nacos控制台修改配置),服务端会发布配置变更事件。
  4. 通知客户端:服务端根据维护的监听关系,通知相关的客户端配置已变更。
  5. 客户端拉取新配置:客户端收到通知后,从服务端拉取新的配置。

2. 关键源码分析思路

2.2 服务端接收配置变更

服务端流程如下:

image.png 请求处理方法,主要做了两件事配置持久化和发布配置变更事件 com.alibaba.nacos.config.server.service.ConfigOperationService#publishConfig

image.png

2.3 服务端接收监听请求

nacos用了很多的事件模式去处理配置的变动,AsyncNotifyService主要是把变动封装成NotifySingleRpcTask交给线程池异步通知集群其他节点哪个配置变更了,因为客户端注册在不同的节点上,这样可以通知到所有的客户端配置变更了。旧版本的这里会判断是否支持rpc 不支持发送http 做了一个兼容 com.alibaba.nacos.config.server.service.notify.AsyncNotifyService#handleConfigDataChangeEvent

image.png

异步task代码 1.构建请求体2.判断节点是否在线不在线不做任何处理3. 检查节点是否健康,健康的发送grpc请求,不健康的重新放入队列

image.png 处理集群内配置变更的RPC通知 这个方法主要就是接收RPC请求对调用DumpService参数处理,最终加到ConcurrentHashMap com.alibaba.nacos.config.server.remote.ConfigChangeClusterSyncRequestHandler#handle ->com.alibaba.nacos.config.server.service.dump.DumpService#dump ->com.alibaba.nacos.common.task.engine.NacosDelayTaskExecuteEngine#addTask

public void addTask(Object key, AbstractDelayTask newTask) {
    lock.lock();
    try {
    // 如果队列存在相同key的任务惊醒合并
        AbstractDelayTask existTask = tasks.get(key);
        if (null != existTask) {
            newTask.merge(existTask);
        }
        tasks.put(key, newTask);
    } finally {
        lock.unlock();
    }
}

上面的代码加到map如何处理的呢?这里不得不说nacos代码很多操作都是异步的 NacosDelayTaskExecuteEngine 负责处理延迟任务,延迟启动一个异步任务 com.alibaba.nacos.common.task.engine.NacosDelayTaskExecuteEngine.ProcessRunnable 方法主要的处理逻辑

protected void processTasks() {
//拿出map里面的所有任务循环执行
    Collection<Object> keys = getAllTaskKeys();
    for (Object taskKey : keys) {
    // 先移除在处理 并且判断了是否够间隔时间
        AbstractDelayTask task = removeTask(taskKey);
        if (null == task) {
            continue;
        }
        NacosTaskProcessor processor = getProcessor(taskKey);
        try {
            // ReAdd task if process failed
            if (!processor.process(task)) {
            // 失败的重新加入map
                retryFailedTask(taskKey, task);
            }
        } catch (Throwable e) {
            getEngineLog().error("Nacos task execute error ", e);
            retryFailedTask(taskKey, task);
        }
    }
}

NacosTaskProcessor 有这么多实现类,处理的是哪个实现类? image.png 因为往map放的时候没有设置处理器,则用的 DumpService默认的DumpProcessor处理 image.png 接下来就是先查询配置然后比较md5更新本地缓存 com.alibaba.nacos.config.server.service.ConfigCacheService#dumpWithMd5

image.png 当配置发生变更时,如通过Nacos控制台的ConfigControllerpublishConfig方法修改配置,这个方法会触发配置变更事件。配置变更事件会被ConfigChangePublisher捕获并发布。

2.4 通知客户端

在修改完内存中md5值后发布配置变更的事件后,会通过RpcConfigChangeNotifier通知相关的客户端。RpcConfigChangeNotifier根据维护的监听关系,RPC向客户端发送通知。 image.png 注意这里只通知哪个配置变更了内容是什么需要客户端拉取,思考为什么这么做? image.png

2.5 客户端拉取新配置

客户端启动会创建 NacosConfigService 初始化 ClientWorker 和ServerHttpAgent

public NacosConfigService(Properties properties) throws NacosException {
    PreInitUtils.asyncPreLoadCostComponent();
    final NacosClientProperties clientProperties = NacosClientProperties.PROTOTYPE.derive(properties);
    ValidatorUtils.checkInitParam(clientProperties);
    
    initNamespace(clientProperties);
    this.configFilterChainManager = new ConfigFilterChainManager(clientProperties.asProperties());
    ServerListManager serverListManager = new ServerListManager(clientProperties);
    serverListManager.start();
    
    this.worker = new ClientWorker(this.configFilterChainManager, serverListManager, clientProperties);
    // will be deleted in 2.0 later versions
    agent = new ServerHttpAgent(serverListManager);
    

创建ClientWorker对象的时候会执行agent.start() 这个方法里面会执行一个延迟周期任务(后面会详细讲)

public ClientWorker(final ConfigFilterChainManager configFilterChainManager, ServerListManager serverListManager,
        final NacosClientProperties properties) throws NacosException {
    this.configFilterChainManager = configFilterChainManager;
    
    init(properties);
    
    agent = new ConfigRpcTransportClient(properties, serverListManager);
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(initWorkerThreadCount(properties),
            new NameThreadFactory("com.alibaba.nacos.client.Worker"));
    agent.setExecutor(executorService);
    agent.start();
    
}

初始化完成nacos 会往ClientWorker 添加监听

public void addListener(String dataId, String group, Listener listener) throws NacosException {
    worker.addTenantListeners(dataId, group, Collections.singletonList(listener));
}

com.alibaba.nacos.client.config.impl.ClientWorker.ConfigRpcTransportClient#startInterna l 就是一个延时周期任务

public void startInternal() {
    executor.schedule(() -> {
        while (!executor.isShutdown() && !executor.isTerminated()) {
            try {
                // 删除头部元素 没有可删除的阻塞超时
                listenExecutebell.poll(5L, TimeUnit.SECONDS);
                if (executor.isShutdown() || executor.isTerminated()) {
                    continue;
                }
                executeConfigListen();
            } catch (Throwable e) {
                LOGGER.error("[rpc listen execute] [rpc listen] exception", e);
                try {
                    Thread.sleep(50L);
                } catch (InterruptedException interruptedException) {
                    //ignore
                }
                // 往队列插入空的对象
                notifyListenConfig();
            }
        }
    }, 0L, TimeUnit.MILLISECONDS);
    
}

主要负责遍历缓存数据,检查其状态并分类处理,然后执行监听和移除监听的检查,并在必要时更新同步时间和通知监听配置的变化com.alibaba.nacos.client.config.impl.ClientWorker.ConfigRpcTransportClient#executeConfigListen 如果有变化往listenExecutebell(阻塞队列) 添加元素,监听的方法会触发又重复调用executeConfigListen方法

image.png

3. 总结

Nacos的配置变动处理流程涉及多个模块和组件的交互,包括客户端SDK、服务端控制器、配置持久化服务、配置变更发布器和配置通知器等。要深入理解这个流程,建议从上述关键部分入手,逐步跟踪和分析源码。同时,也可以参考Nacos的官方文档和社区资源,以便更全面地了解其设计和实现原理。

由于源码分析涉及大量细节,这里仅提供了大致的思路和关键部分。如有需要,可以进一步深入研究和探讨。