【技术挑战】Nacos自动刷新配置如何实现的?

由浏览前F12请求情况可知,更新配置,会调用 POST:/nacos/v1/cs/configs API,具体的方法时 ConfigController#publishConfig 方法:

通过上述代码可知,修改配置后,服务端先通过 persistService.insertOrUpdate 将配置信息进行了更新,然后调用 EventDispatcher#fireEvent 触发一个 ConfigDataChangeEvent 事件。接下来,我们来查看一下fireEvent方法主要做了什么:

由此可见,fireEvent主要是根据事件class获取listener列表( CopyOnWriteArrayList ),然后循环调用每个listener的onEvent方法。而listener是通过 EventDispatcher#addEventListener 添加到listeners中。因此,我们只需找到调用 EventDispatcher#addEventListener 方法的地方,即可得知需要触发哪些 AbstractEventListener 的onEvent回调方法。

我们发现 AbstractEventListener 构造方法调用了 EventDispatcher.addEventListener(this) 方法,但是显然 AbstractEventListener 是抽象类,应找具体的实现类,很巧,一个熟悉的身影出现在面前: LongPollingService

所以,我们可以显而易见两条线路:

(1)在nacos控制台更改配置信息时 -> 调用 POST:/nacos/v1/cs/configs API -> 触发 ConfigDataChangeEvent 事件 -> 调用listener的onEvent方法 -> LongPollingService#onEvent

(2)在nacos控制台更改配置信息时 -> 调用 POST:/nacos/v1/cs/configs API -> 触发 ConfigDataChangeEvent 事件 -> 调用listener的onEvent方法 -> AsyncNotifyService#onEvent

那到底哪个是我们想要的呢?还记得我们触发的事件类型吗?没错,是 ConfigDataChangeEvent 事件,所以我们只需对比两条线路哪个是处理的该类型事件即可。

经过上述 LongPollingService#onEventAsyncNotifyService#onEvent 初步查看,基本可以确定 AsyncNotifyService 是我们想要的,但是事实真的是如此吗?我们通过查看 AsyncNotifyService.AsyncTask#executeAsyncInvoke 中并没有任何相关性。那么你可能会说, LongPollingService#onEvent 处理的是 LocalDataChangeEvent 岂不是更不相关?

然而答案却是 LongPollingService ,我们不要被表象所迷惑了。为什么呢?在Nacos中有一个DumpService,它会定时把变更后的数据dump到磁盘上。DumpService在Spring启动之后,会调用init方法启动几个dump任务,然后在任务执行结束之后,会触发一个LocalDataChangeEvent 的事件。

我们来看一下代码流转过程吧:

代码流程过程大致为: DumpService#init -> DumpAllProcessor#process -> ConfigService#dump -> ConfigService#updateMd5 -> EventDispatcher.fireEvent(new LocalDataChangeEvent(groupKey))

所以,我们还是要会回到 LongPollingService#onEvent 中,其中会启动一个 DataChangeTask 线程,其中会有一个循环迭代器从 allSubs 里面获取 ClientLongPolling 对象,删除订阅关系后,调用 ClientLongPolling#sendResponse 将数据返回给客户端,这就是为什么配置信息可以实时触发更新的缘由了。

那如果 DataChangeTask 任务完成了数据返回客户端后, ClientLongPolling 中延迟任务开始执行怎么办?

haha~No Problem.因为在 DataChangeTask 调用 ClientLongPolling#sendResponse 返回数据给客户端时,会先取消超时任务,然后再反馈数据给客户端。代码如下: