Nacos 作为配置中心怎么实现配置更新后动态同步到项目中

Nacos可以作为注册中心也可以作为配置中心,今天看一下作为配置中心,更新了配置,而运行中的项目不用重启就可以加载到更新后的配置。其实这是Nacos的一大特点。


一、先看下Nacos的三大功能有哪些

二、作为配置中心如何实现动态配置服务的

因为想了解怎么实现动态配置的过程太长了,所以先把结论高度的概括下,先知道答案,至于过程大家有时间有精力的慢慢看。


客户端采用长轮训的方式定时向服务端发起 Pull 请求,去检查服务端配置信息是否发生了变更。

如果发生了变更,则客户端会根据变更的数据获得最新的配置。

如果没有发生变更,即服务端的配置和客户端的配置是保持一致的,那么服务端会先 “Hold” 住这个请求,也就是服务端拿到这个连接之后在指定的时间段内一直不返回结果,直到这段时间内配置发生变化,服务端会把原来 “Hold” 住的请求进行返回。明细如下

Nacos 服务端收到请求之后,先检查配置是否发生了变更,如果没有,则设置一个定时任务,延期 29.5s 执行,并且把当前的客户端长轮训加入 allSubs 队列。这个时候有两种方式触发该链接结果的返回。

1、第一种是在等待 29.5s 后触发自动检查机制,这个时候不管配置有没有发生变化,都会把结果返回客户端。而 29.5s 就是这个长连接保持的时间。

2、第二种是在 29.5s 内任意一个时刻,通过 Nacos Dashboard 或者 API 的方式对配置进行了修改,就会触发一个事件机制,监听该事件的任务会遍历 allSubs 队列,找到发生变更的配置项对应的 ClientLongPolling 任务,将变更的数据通过该任务的连接进行返回,就完成一次 “推送” 操作。

这样既能够保证客户端实时感知配置的变化,也降低了服务端的压力。其中,这个长连接的回话超时时间默认是 30s


名词解释:


ClientLongPolling :调度任务


客户端长轮询

客户端长轮询的部分,也就是LongPollingRunnable中的checkUpdateDataIds 方法,该方法就是用来访问服务端的配置是否发生变更的,该方法最终会调用如下图所示的方法:

http请求操作

客户端是通过一个http的post 请求去获取服务端的结果的,并且设置了一个超时时间:30s。一般来讲:客户端足足等了29.5+s,才请求到服务端的结果,然后客户端得到服务端的结果之后,再做一些后续的操作,全部都执行完毕之后,在 finally 中又重新调用了自身,也就是说这个过程是一直循环下去的。

长轮询执行逻辑

客户端向服务端发起一次请求,最少要29.5s才能得到结果,当然啦,这是在配置没有发生变化的情况下。如果客户端在长轮询时配置发生变更的话,该请求需要多长时间才会返回呢,在客户端长轮询时修改配置。

未获得到修改数据的操作触发返回
获得到了修改数据操作立刻触发返回
服务端controller

上面说到了客户端发送的 http 请求中可以知道,请求的是服务端的 /v1/cs/configs/listener 的这个接口

com.alibaba.nacos.config.server.controller.ConfigController.java,在 ConfigController 类中,如下图所示:

服务端是通过springMVC对外提供的 http 服务,对 HttpServletRequest 中的参数进行转换后,然后交给一个叫 inner 的对象去执行。inner 对象是 ConfigServletInnerleie 类的实例

com.alibaba.nacos.config.server.controller.ConfigServletInner.java

该方法是一个轮询的接口,除了支持长轮询外还支持短轮询的逻辑。再次进入 longPollingService 的 addLongPollingClient 方法,如下图所示:

com.alibaba.nacos.config.server.service.LongPollingService.java

该方法主要是将客户端的长轮询请求添加到某个东西中去:服务端将客户端的长轮询请求封装成一个叫 ClientLongPolling 的任务,交给 scheduler 去执行。

服务端拿到客户端提交的超时时间后,又减去了 500ms 也就是说服务端在这里使用了一个比客户端提交的时间少 500ms 的超时时间,也就是 29.5s,看到这个 29.5s 我们应该有点兴奋了。

PS:这里的 timeout 不一定一直是 29.5,当 isFixedPolling() 方法为 true 时,timeout 将会是一个固定的间隔时间,这里为了描述简单就直接用 29.5 来进行说明。

接下来我们来看服务端封装的 ClientLongPolling 的任务到底执行的什么操作,如下图所示:

com.alibaba.nacos.config.server.service.LongPollingService.ClientLongPolling.java

ClientLongPolling 被提交给 scheduler 执行之后,实际执行的内容可以拆分成以下四个步骤:

  1. 创建一个调度的任务,调度的延时时间为 29.5s。

  2. 将该 ClientLongPolling 自身的实例添加到一个 allSubs 中去。

  3. 延时时间到了之后,首先将该 ClientLongPolling 自身的实例从 allSubs 中移除。

  4. 获取服务端中保存的对应客户端请求的 groupKeys 是否发生变更,将结果写入 response 返回给客户端。

allSubs 对象,该对象是一个 ConcurrentLinkedQueue 队列,ClientLongPolling 将自身添加到队列中。

调度任务

服务端对客户端提交上来的 groupKey 进行检查,如果发现某一个 groupKey 的 md5 值还不是最新的,则说明客户端的配置项还没发生变更,所以将该 groupKey 放到一个 changedGroupKeys 列表中,最后将该 changedGroupKeys 返回给客户端。对于客户端来说,只要拿到 changedGroupKeys 即可。

服务端数据变更

服务端直到调度任务的延时时间到了之前,ClientLongPolling 都不会有其他的任务可做,所以在这段时间内,该 allSubs 队列肯定有事情需要进行处理。

在客户端长轮询期间,更改了配置之后,客户端能够立即得到响应。

服务端数据变更接口

调用的请求,可以很容易的找到该请求对应的 url为:/v1/cs/configs 并且是一个 POST 请求,具体的方法是 ConfigController 中的 publishConfig 方法,如下图所示:

修改配置后,服务端首先将配置的值进行了持久化层的更新,然后触发了一个 ConfigDataChangeEvent 的事件,fireEvent 的方法:

com.alibaba.nacos.config.server.utils.event.EventDispatcher.java

fireEvent 方法实际上是触发的 AbstractEventListener 的 onEvent 方法,而所有的 listener 是保存在一个叫 listeners 对象中的。

被触发的 AbstractEventListener 对象则是通过 addEventListener 方法添加到 listeners 中的,找到 addEventListener 方法在何处被调用的,就知道有哪些 AbstractEventListener 需要被触发 onEvent 回调方法了。

可以找到是在 AbstractEventListener 类的构造方法中,将自身注册进去了,如下图所示:

com.alibaba.nacos.config.server.utils.event.EventDispatcher.AbstractEventListener.java

可以看到 AbstractEventListener 所有的子类中LongPollingService。当我们从 dashboard 中更新了配置项之后,实际会调用到 LongPollingService 的 onEvent 方法。

回到 LongPollingService 中,查看一下 onEvent 方法,如下图所示:

com.alibaba.nacos.config.server.service.LongPollingService.DataChangeTask.java

发现当触发了 LongPollingService 的 onEvent 方法时,实际是执行了一个叫 DataChangeTask 的任务,应该是通过该任务来通知客户端服务端的数据已经发生了变更,我们进入 DataChangeTask 中看下具体的代码,如下图所示:

遍历 allSubs 的队列

遍历 allSubs 的队列,该队列中维持的是所有客户端的请求任务,需要找到与当前发生变更的配置项的 groupKey 相等的 ClientLongPolling 任务

往客户端写响应数据

丢i与ClientLongPolling 任务后,只需要将发生变更的 groupKey 通过该 ClientLongPolling 写入到响应对象中,就完成了一次数据变更的 “推送” 操作了

如果 DataChangeTask 任务完成了数据的 “推送” 之后,需要将原来等待执行的调度任务取消掉了,这样就防止了推送操作写完响应数据之后,调度任务又去写响应数据。

可以从 sendResponse 方法中看到,确实是这样做的:

http请求本来就是无状态的,所以没必要也不能将超时时间设置的太长,这样是对资源的一种浪费。

与此同时服务端也将该请求封装成一个调度任务去执行,等待调度的期间就是等待 DataChangeTask 主动触发的,如果延迟时间到了 DataChangeTask 还未触发的话,则调度任务开始执行数据变更的检查,然后将检查的结果写入响应对象,如下图所示:

总结结论:

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/242937.html

联系我们
联系我们
分享本页
返回顶部