上篇文章《Nacos 配置中心原理分析 》我和你们分析了 Nacos 的配置中心原理,主要分析了 Nacos 客户端是如何感知到服务端的配置变动的,可是只是从客户端的角度进行了分析,并无从服务端的角度进行分析,本篇文章我将结合服务端从两个角度来分析配置变动是如何通知到客户端的。java
PS:文章有点长,由于涉及到多个细节须要阐述,若是看不下去的话,能够直接转到文末看总结便可。spring
1、客户端
从上篇文章中咱们已经知道了 Nacos 的客户端维护了一个长轮询的任务,去检查服务端的配置信息是否发生变动,若是发生了变动,那么客户端会拿到变动的 groupKey 再根据 groupKey 去获取配置项的最新值便可。post
每次都靠客户端去发请求,询问服务端我所关注的配置项有没有发生变动,那请求的间隔改设置为多少才合适呢?url
若是间隔时间设置的太长的话有可能没法及时获取服务端的变动,若是间隔时间设置的过短的话,那么频繁的请求对于服务端来讲无疑也是一种负担。spa
因此最好的方式是客户端每隔一段长度适中的时间去服务端请求,而在这期间若是配置发生变动,服务端可以主动将变动后的结果推送给客户端,这样既能保证客户端可以实时感知到配置的变化,也下降了服务端的压力。3d
客户端长轮询
如今让咱们再次回到客户端长轮询的部分,也就是 LongPollingRunnable 中的 checkUpdateDataIds 方法,该方法就是用来访问服务端的配置是否发生变动的,该方法最终会调用以下图所示的方法:日志
请注意图中红框部分的内容,客户端是经过一个 http 的 post 请求去获取服务端的结果的,而且设置了一个超时时间:30s。server
这个信息很关键,为何客户端要等待 30s 才超时呢?不该该越快获得结果越好吗,咱们来验证下该方法是否是真的等待了 30s。对象
在 LongPollingRunnable 中的 checkUpdateDataIds 方法先后加上时间计算,而后将所消耗的时间打印出来,以下图所示:blog
而后咱们启动客户端,观察打印的日志,以下图所示:
从打印出来的日志能够看出来,客户端足足等了29.5+s,才请求到服务端的结果。而后客户端获得服务端的结果以后,再作一些后续的操做,所有都执行完毕以后,在 finally 中又从新调用了自身,也就是说这个过程是一直循环下去的。
长轮询时修改配置
如今咱们能够肯定的是,客户端向服务端发起一次请求,最少要29.5s才能获得结果,固然啦,这是在配置没有发生变化的状况下。
若是客户端在长轮询时配置发生变动的话,该请求须要多长时间才会返回呢,咱们继续作一个实验,在客户端长轮询时修改配置,结果以下图所示:
上图中红框中就是我在客户端一发起请求时就更新配置后打印的结果,从结果能够看出来该请求并无等到 29.5s+ 才返回,而是一个很短的时间就返回了,具体多久须要从服务端的实现中查询答案。
到目前为止咱们已经知道了客户端执行长轮询的逻辑,以及每次请求的响应时间会随着服务端配置是否变动而发生变化,具体能够用下图描述:
2、服务端
分析完客户端的状况,接下来要重点分析服务端是如何实现的,而且要带着几个问题去寻找答案:
客户端长轮询的响应时间会受什么影响
为何更改了配置信息后客户端会当即获得响应
客户端的超时时间为何要设置为30s
带着以上这些问题咱们从服务端的代码中去探寻结论。
首先咱们从客户端发送的 http 请求中能够知道,请求的是服务端的 /v1/cs/configs/listener 这个接口。
咱们找到该接口对应的方法,在 ConfigController 类中,以下图所示:
com.alibaba.nacos.config.server.controller.ConfigController.java
Nacos 的服务端是经过 spring 对外提供的 http 服务,对 HttpServletRequest 中的参数进行转换后,而后交给一个叫 inner 的对象去执行。
下面咱们进入这个叫 inner 的对象中去,该 inner 对象是 ConfigServletInner 类的实例,具体的方法以下所示:
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 将自身添加到队列中去确定是有缘由的,这里须要对 allSubs 留个心眼。
调度任务
咱们先无论 allSubs 队列具体作了什么事,先来看下服务端过了 29.5s 的延时时间后,执行调度任务时作了什么,也就是上图中对应的第3、第四步。
首先将自身从 allSubs 队列中删除掉,也就是如注释中说的:删除订阅关系,从这里咱们能够知道 allSubs 和 ClientLongPolling 之间维持了一种订阅关系,而 ClientLongPolling 是被订阅的。
PS:删除掉订阅关系以后,订阅方就没法对被订阅方进行通知了。
而后服务端对客户端提交上来的 groupKey 进行检查,若是发现某一个 groupKey 的 md5 值还不是最新的,则说明客户端的配置项还没发生变动,因此将该 groupKey 放到一个 changedGroupKeys 列表中,最后将该 changedGroupKeys 返回给客户端。
对于客户端来讲,只要拿到 changedGroupKeys 便可,后续的操做我在上一篇文章中已经分析过了。
服务端数据变动
服务端直到调度任务的延时时间到了以前,ClientLongPolling 都不会有其余的任务可作,因此在这段时间内,该 allSubs 队列确定有事情须要进行处理。
回想到咱们在客户端长轮询期间,更改了配置以后,客户端可以当即获得响应,因此咱们有理由相信,这个队列可能会跟配置变动有关系。
如今咱们找一下在 dashboard 上修改配置后,调用的请求,能够很容易的找到该请求对应的 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 是一个抽象类,因此实际注册的应该是 AbstractEventListener 的子类,因此咱们须要找到因此继承自 AbstractEventListener 的类,以下图所示:
能够看到 AbstractEventListener 全部的子类中,有一个咱们熟悉的身影,他就是咱们刚刚一直在研究的 LongPollingService。
因此到这里咱们就知道了,当咱们从 dashboard 中更新了配置项以后,实际会调用到 LongPollingService 的 onEvent 方法。
如今咱们继续回到 LongPollingService 中,查看一下 onEvent 方法,以下图所示:
com.alibaba.nacos.config.server.service.LongPollingService.DataChangeTask.java
发现当触发了 LongPollingService 的 onEvent 方法时,实际是执行了一个叫 DataChangeTask 的任务,应该是经过该任务来通知客户端服务端的数据已经发生了变动,咱们进入 DataChangeTask 中看下具体的代码,以下图所示:
代码很简单,能够总结为两个步骤:
1.遍历 allSubs 的队列
首先遍历 allSubs 的队列,该队列中维持的是全部客户端的请求任务,须要找到与当前发生变动的配置项的 groupKey 相等的 ClientLongPolling 任务
2.往客户端写响应数据
在第一步找到具体的 ClientLongPolling 任务后,只须要将发生变动的 groupKey 经过该 ClientLongPolling 写入到响应对象中,就完成了一次数据变动的 “推送” 操做了
若是 DataChangeTask 任务完成了数据的 “推送” 以后,ClientLongPolling 中的调度任务又开始执行了怎么办呢?
很简单,只要在进行 “推送” 操做以前,先将原来等待执行的调度任务取消掉就能够了,这样就防止了推送操做写完响应数据以后,调度任务又去写响应数据,这时确定会报错的。
能够从 sendResponse 方法中看到,确实是这样作的:
问题解答
如今让咱们回到刚开始的时候提的几个问题,相信你们已经有了答案了。
客户端长轮询的响应时间会受什么影响
客户端长轮询的响应时间,设置的是30s,可是有时响应很快,有时响应很慢,这取决于服务端的配置有没有发生变化。当配置发生变化时,响应很快就会返回,当配置一直没有发生变化时,会等到 29.5s 以后再进行响应。
为何更改了配置信息后客户端会当即获得响应
由于服务端会在更改了配置信息后,找到具体的客户端请求中的 response,而后直接将结果写入 response 中,就像服务端对客户端进行的数据 “推送” 同样,因此客户端会很快获得响应。
客户端的超时时间为何要设置为30s
这应该是一个经验值,该超时时间关系到服务端调度任务的等待时间,服务端在前29.5s 只须要进行等待,最后的 0.5s 才进行配置变动检查。
若是设置的过短,那服务端等待的时间就过短,若是这时配置变动的比较频繁,那极可能没法在等待期对客户端作推送,而是滑动到检查期对数据进行检查后才能将数据变动发回给客户端,检查期相比等待期须要进行数据的检查,涉及到 IO 操做,而 IO 操做是比较昂贵的,咱们应该尽可能在等待期就将数据变动发送给客户端。
http 请求原本就是无状态的,因此不必也不能将超时时间设置的太长,这样是对资源的一种浪费。
总结
一、客户端的请求到达服务端后,服务端将该请求加入到一个叫 allSubs 的队列中,等待配置发生变动时 DataChangeTask 主动去触发,并将变动后的数据写入响应对象,以下图所示:
二、与此同时服务端也将该请求封装成一个调度任务去执行,等待调度的期间就是等待 DataChangeTask 主动触发的,若是延迟时间到了 DataChangeTask 还未触发的话,则调度任务开始执行数据变动的检查,而后将检查的结果写入响应对象,以下图所示:
基于上述的分析,最终总结了如下结论:
1.Nacos 客户端会循环请求服务端变动的数据,而且超时时间设置为30s,当配置发生变化时,请求的响应会当即返回,不然会一直等到 29.5s+ 以后再返回响应
2.Nacos 客户端可以实时感知到服务端配置发生了变化。
3.实时感知是创建在客户端拉和服务端“推”的基础上,可是这里的服务端“推”须要打上引号,由于服务端和客户端直接本质上仍是经过 http 进行数据通信的,之因此有“推”的感受,是由于服务端主动将变动后的数据经过 http 的 response 对象提早写入了。
至此,正如标题所说的,推+拉打造 Nacos 配置信息的实时更新的原理已经分析清楚了。
逅弈逐码,专一于原创分享,用通俗易懂的图文描述源码及原理