1. 概述
Nacos的配置变动处理流程大致包括以下几个步骤:
- 客户端监听配置:客户端通过Nacos的SDK或API监听特定的配置。
- 服务端接收监听请求:Nacos服务端接收客户端的监听请求,并维护监听关系。
- 配置变更发布:当配置发生变更时(如通过Nacos控制台修改配置),服务端会发布配置变更事件。
- 通知客户端:服务端根据维护的监听关系,通知相关的客户端配置已变更。
- 客户端拉取新配置:客户端收到通知后,从服务端拉取新的配置。
2. 关键源码分析思路
2.2 服务端接收配置变更
服务端流程如下:
请求处理方法,主要做了两件事配置持久化和发布配置变更事件
com.alibaba.nacos.config.server.service.ConfigOperationService#publishConfig
2.3 服务端接收监听请求
nacos用了很多的事件模式去处理配置的变动,AsyncNotifyService主要是把变动封装成NotifySingleRpcTask交给线程池异步通知集群其他节点哪个配置变更了,因为客户端注册在不同的节点上,这样可以通知到所有的客户端配置变更了。旧版本的这里会判断是否支持rpc 不支持发送http 做了一个兼容 com.alibaba.nacos.config.server.service.notify.AsyncNotifyService#handleConfigDataChangeEvent
异步task代码 1.构建请求体2.判断节点是否在线不在线不做任何处理3. 检查节点是否健康,健康的发送grpc请求,不健康的重新放入队列
处理集群内配置变更的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 有这么多实现类,处理的是哪个实现类?
因为往map放的时候没有设置处理器,则用的 DumpService默认的DumpProcessor处理
接下来就是先查询配置然后比较md5更新本地缓存
com.alibaba.nacos.config.server.service.ConfigCacheService#dumpWithMd5
当配置发生变更时,如通过Nacos控制台的
ConfigController的publishConfig方法修改配置,这个方法会触发配置变更事件。配置变更事件会被ConfigChangePublisher捕获并发布。
2.4 通知客户端
在修改完内存中md5值后发布配置变更的事件后,会通过RpcConfigChangeNotifier通知相关的客户端。RpcConfigChangeNotifier根据维护的监听关系,RPC向客户端发送通知。
注意这里只通知哪个配置变更了内容是什么需要客户端拉取,思考为什么这么做?
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方法
3. 总结
Nacos的配置变动处理流程涉及多个模块和组件的交互,包括客户端SDK、服务端控制器、配置持久化服务、配置变更发布器和配置通知器等。要深入理解这个流程,建议从上述关键部分入手,逐步跟踪和分析源码。同时,也可以参考Nacos的官方文档和社区资源,以便更全面地了解其设计和实现原理。
由于源码分析涉及大量细节,这里仅提供了大致的思路和关键部分。如有需要,可以进一步深入研究和探讨。