原文:https://blog.csdn.net/Zong_0915/article/details/
作者:Zong_0915
首先,Nacos Config针对配置的管理提供了4种操作):
获取配置,从Nacos Config Server中读取配置。
监听配置:订阅感兴趣的配置,当配置发生变化的时候可以收到一个事件。
发布配置:将配置保存到Nacos Config Server中。
删除配置:删除配置中心的指定配置。
而从原理层面来看,可以归类为两种类型:配置的CRUD和配置的动态监听。
对于Nacos Config来说,主要是提供了配置的集中式管理功能,然后对外提供CRUD的访问接口使得应用系统可以完成配置的基本操作。
对于服务端来说:需要考虑的是配置如何存储,是否需要持久化。
对于客户端来说:需要考虑的是通过接口从服务器查询得到相应的数据然后返回。
关系如下:
注意:
Nacos服务端的数据存储默认采用的是Derby数据库(也支持Mysql)。
Nacos的客户端和服务端之间存在着数据交互的一种行为(不然怎么做到实时的更新和数据的查询呢),而对于这种交互行为共有两种方式:
Nacos采用的是Pull模式(Kafka也是如此),并且采用了一种长轮询机制。客户端采用长轮询的方式定时的发起Pull请求,去检查服务端配置信息是否发生了变更,如果发生了变更,那么客户端会根据变更的数据获得最新的配置。
长轮询:客户端发起轮询请求后,服务端如果有配置发生变更,就直接返回。
如下图:
详细地来说:
如果客户端发起Pull请求后,发现服务端的配置和客户端的配置是保持一致的,那么服务端会“Hold”住这个请求。(服务端拿到这个连接后在指定的时间段内不会返回结果,直到这段时间内的配置发生变化)
一旦配置发生了变化,服务端会把原来“Hold”住的请求进行返回。
工作流程图如下:
对于流程图解释如下:
Nacos服务端收到请求后,会检查配置是否发生了变更,如果没有,那么设置一个定时任务,延期29.5秒执行。同时并且把当前的客户端长轮询连接加入到allSubs队列。 这时候有两种方式触发该连接结果的返回:
首先需要了解到,SpringCloud是基于Spring来扩展的,而Spring本身就提供了Environment,用来表示Spring应用程序的环境配置(包括外部环境),并且提供了统一访问的方法getProperty(String key)来获取配置。
对于SpringCloud而言,要实现统一配置管理并且动态的刷新配置,需要解决两个问题:
如何将远程服务器上的配置(Nacos Config Server)加载到Environment上。
配置变更时,如何将新的配置更新到Environment中。
对于配置的加载而言,需要牵扯到SpringBoot的自动装配,进行环境的准备工作:
环境的准备
1.在SpringBoot启动的时候,SpringApplication.run()方法会进行环境准备的工作,来看下该方法(只显示和环境配置相关的代码)
2.重点来看这个方法:
3.监听事件后的处理,进行了自动装配:
4.自动装配的类:
下的
以及包下
下的:
那么会对这两个类进行自动装载。
6.回到步骤中,到这里,SpringApplication.run()中的环境准备已经完成了,那么和我们的服务加载有啥关系嘞?环境的准备完成了,意味着相对应的类已经完成了初始化,而其中有一个类就叫做PropertySourceBootstrapConfiguration,他是一个启动环境配置类,他的初始化就是通过上述自动装配来完成的!
7.而PropertySourceBootstrapConfiguration类中就有一个initialize()方法,会调用PropertySourceLocators.locate()来获取远程配置信息,那么接下来就开始讲环境的加载
环境的加载
1.上文SpringApplication.run()方法中已经介绍完了环境的准备工作,接下里就要进行配置的加载了,继续从该方法出发(tips:这里和上文的区别,多了一行代码this.prepareContext(),同样为了方便,把不必要的代码省去了):
2.prepareContext()方法主要是进行应用上下文的一个准备:
3.第二步代码中出现了一个接口:,那么最终代码的实现肯定是要跑其子类的代码,
5.PropertySourceLocator接口的主要作用:实现应用外部化配置可动态加载,而NacosPropertySourceLocator实现了该接口。因此最终会调用NacosPropertySourceLocator中的locate()方法,实现把Nacos服务上的代码进行加载。
6.NacosPropertySourceLocator.locate()方法最终得到配置中心上的配置并通过对象封装来返回:
到这里,配置的加载流程也就完成了,对于具体加载的内容(也就是this.loadxxx()方法的具体实现)放在2.2节详细介绍,接下来通过案例来再一次理解这个过程。
案例1:通过Debug来理解Config的配置加载
1.容器启动后,执行SpringApplication.run()方法:
2.开始准备环境:
3.此时类监听到事件(这个事件指的是环境准备事件),执行方法:
4.该方法最后会执行builder.sources(),引入选择器Selector:
5.环境准备完毕,开始准备应用上下文的信息:
6.执行applyInitializers()方法,最终调用到PropertySourceBootstrapConfiguration的initialize()方法,这里则调用了Nacos相关的配置类的初始化。
7.由于NacosPropertySourceLocator实现了PropertySourceLocator接口,因此调用其locate()方法,将从Nacos Config Server获得的配置封装成CompositePropertySource对象进行返回。
小总结1☆
Nacos Config的配置加载过程如下:
SpringBoot项目启动,执行SpringApplication.run()方法,先对项目所需的环境做出准备
BootstrapApplicationListener监听器监听到环境准备事件,对需要做自动装配的类进行载入。,导入BootstrapImportSelectorConfiguration配置类,该配置类引入BootstrapImportSelector选择器,完成相关的初始化操作。
环境准备完成后(所需的相关配置类也初始化完成),执行方法this.prepareContext()完成上下文信息的准备。
this.prepareContext()需要对相关的类进行初始化操作。由于PropertySourceBootstrapConfiguration类实现了ApplicationContextInitializer接口。因此调用其initialize()方法,完成初始化操作。
对于PropertySourceBootstrapConfiguration下的初始化操作,需要实现应用外部化配置可动态加载,而NacosPropertySourceLocator 实现了PropertySourceLocator接口,故执行他的locate()方法。
最终NacosPropertySourceLocator的locate()方法完成从Nacos Config Server上加载配置信息。
写到这里,其实目前为止主要讲的是SpringCloud项目从启动到执行方法获取配置的这么一个过程,而对于具体获取远程配置的代码实现并没有深入去讲解。即上文的locate()方法,而该方法还涉及到配置更新时,Nacos如何去做到监听的操作,因此准备将这一块内容另起一节来讲解。
1.这里我们从NacosPropertySourceLocator类下的locate()方法开始分析:
2.因为一般来说我们都是根据应用名称来获取配置,所以这里以方法为例来说。
这里总结下方法的执行流程(关注最后的方法即可!,这里列出流程中所有涉及到的步骤是为了怕大家不清楚方法的调用顺序):
到这一步我们只需了解到,加载的具体操作是交给ConfigService(当然,它是个接口,具体实现交给NacosConfigService来完成)来加载配置的。(后面会Debug来具体查看)
接下来主要开始说明NacosConfig的事件订阅机制的实现,分为多个角度结合上文的图来进行说明:
如何监听到事件的变更。
NacosConfigService的初始化。(配置加载方法的执行者)
ClientWorker。(因为根据上文的说法,Nacos会有一个定时调度的任务存在,而其具体的实现是NacosConfigService)
ClientLongRunnable类有什么用。
服务端的长连接实现
ClientLongPolling是什么。
接下来的内容可能比较多,也希望大家能够耐心的看下去,我最后还会进行一个大总结,将核心代码进行一个梳理。
2.2.1 事件订阅机制的实现
我在2.1节讲到了SpringBoot在启动的时候,会执行准备上下文的这么一个操作。而Nacos有一个类叫做NacosContextRefresher,它实现了ApplicationListener,即他是一个监听器,负责监听准备上下文的事件,我们来看下他的结构:
紧接着来看下它的事件监听注册方法:
监听刷新事件的监听器:
2.2.2 NacosConfigService
先来看一下的构造:
竟然都提到了装饰器模式了,那我也啰嗦几句吧🤣,讲一讲什么是装饰器模式:
按照单一职责原则,某一个对象只专注于干一件事,而如果要扩展其职能的话,不如想办法分离出一个类来“包装”这个对象,而这个扩展出的类则专注于实现扩展功能。装饰器模式就可以将新功能动态地附加于现有对象而不改变现有对象的功能。
2.2.3 ClientWorker
同样的,我们来看下其构造函数,其主要作用是构建两个定时调度的线程池,并且启动一个定时任务。
紧接着我们来看下启动的定时任务中,执行的checkConfigInfo() 方法:
上面方法中,总结就是启动一个定时任务,然后通过线程池去建立长轮询连接,检查/更新方法的配置。而具体的任务都在类中了。
上面方法中,总结就是启动一个定时任务,然后通过线程池去建立长轮询连接,检查/更新方法的配置。而具体的任务都在LongPollingRunnable类中了。
2.2.4 LongPollingRunnable
LongPollingRunnable本质上是一个线程,因此直接看他的run()方法。
上述方法总结就是:
根据taskId对cacheMap进行数据的分割,再比较本地配置的数据是否存在变更。
如果存在变更,则直接触发通知。
对于配置的比较,需要注意:在${user} acosconfig目录下会缓存一份服务端的配置信息,而checkLocalConfig()方法会和本地磁盘中的文件内容进行比较。
接下来调用的checkUpdateDataIds()方法,则基于长连接的方式来监听服务端配置的变化,最后根据变化数据的key来去服务端获取最新的数据。(key是dataId/group/租户)
那么接下来再重点讲一讲checkUpdateDataIds()方法,该方法最终会调用checkUpdateConfigStr()方法:
2.2.5 服务端长连接处理机制
tips:这里我去Github上摘抄的代码,地址给大家:Nacos-Server,建议大家下载过来,在Idea中直接看比较方便。
前面主要是讲了事件的订阅、WorkClient创建出的线程池干了什么、以及长连接的建立,但是这些都是面向客户端的,因此接下来从服务端的角度来看一看长连接的处理机制。
在Nacos-config模块下,controller包下有一个类叫做ConfigController,专门用来实现配置的基本操作,其中有一个/listener接口,是客户端发起数据监听的接口。
主要干两件事:
- 获取客户端需要监听的可能发生变化的配置,并计算其MD5值。
- 开始执行长轮询的请求。
接下来来看一下处理长轮询的方法:
即将客户端的长轮询请求封装成交给执行。
2.2.6 ClientLongPolling
这里可以这么理解:
allSubs这个队列和ClientLongPolling之间维持了一种订阅关系,而ClientLongPolling是属于被订阅的角色。
那么一旦订阅关系删除后,订阅方就无法对被订阅方进行通知了。
服务端直到调度任务的延时时间用完之前,ClientLongPolling都不会有其他的事情可以做,因此这段时间内allSubs队列会处理相关的逻辑。
为了我们在客户端长轮询期间,一旦更改配置,客户端能够立即得到响应数据,因此这个事件的触发肯定需要发生在服务端上。看下ConfigController下的publishConfig()方法
我在这里直接以截图的形式来展示重要的代码逻辑:
其实老版本的话,这里的代码写的是(当然我们并不会关注老版本,所以大家了解下就是):
也因此,上文的图中,有一个这么一个字段。
到这里,如果我们从Nacos控制台上更新了某个配置项后,这里会调用的方法:
意思就是通过这个任务来通知客户端:”服务端的数据已经发生了变更!“,接下来看下这个任务干了什么:
- 为什么更改了配置信息后客户端会立即得到响应?
1.首先每个配置在服务端都封装成一个ClientLongPolling对象。其存储于队列当中。
2.客户端和服务端会建立起一个长连接,并且维持29.5秒的等待时间,这段时间内除非配置发生更改,请求是不会返回的。
3.其次服务端一旦发现配置信息发生更改,在更改了配置信息后,会找到对应的ClientLongPolling任务,并将其更改后的groupKey写入到响应对象中response,进行立刻返回。
4.之所以称之为实时的感知,是因为服务端主动将变更后的数据通过HTTP的response对象写入并且立刻返回。
5.而服务端说白了,就是做了一个定时调度任务,在等待调度任务执行的期间(29.5秒)若发生配置变化,则立刻响应,否则等待30秒去返回配置数据给客户端。
接下来开始说Nacos Config实时更新的一个原理:
这里,我同样的准备从多个方面来进行阐述,毕竟内容比较多,也怕大家搞混。
首先,对于客户端而言,如何感知到服务端配置的变更呢?
同样的,当SpringBoot项目启动的时候,会执行”准备上下文“的这么一个事情。此时NacosContextRefresher会监听到这个事件,并且注册一个负责监听配置变更回调的监听器registerNacosListener。
registerNacosListener一旦收到配置变更的回调,则发布一个RefreshEvent事件,对应的RefreshEventListener监听器检测到该事件后,将调用refresh.refresh()方法来完成配置的更新。
一旦发现服务端配置的变更,那么客户端肯定是要再进行配置的加载(locate())的,而其最终通过NacosConfigService.getConfig()方法来实现,在调用这个方法之前,必定要完成NacosConfigService的初始化操作。 因此这个初始化过程做了什么?
根据NacosConfigService的构造函数,其做了两件事:初始化并启动一个HttpAgent(在Client端用来管理链接的持久性和重用的工具),初始化一个ClientWorker。
初始化ClientWorker的过程中,构建了两个定时调度的线程池executor和executorService,并且启动executor线程池,负责定时调度checkConfigInfo()方法,即检查一次配置信息。
checkConfigInfo()方法中,使用了第二步的executorService线程池,负责搭建一个长轮询机制,去监听变更的数据。而这个任务通过LongPollingRunnable类来实现。
LongPollingRunnable是一个线程任务,通过调用checkUpdateDataIds()方法,基于长连接的方式来监听服务端配置的变化 ,同时,如果发生配置的变更,则触发一个个事件,那么上述的监听器发现后,则调用refresh()方法更新配置。
checkUpdateDataIds()方法中,建立的长连接时长30秒,并且一旦服务端发生数据变更,客户端则收到一个HttpResult,里面保存的是这个变更配置的最新key。那么客户端则根据最新配置的key去服务端获取配置。
到这里为止,客户端的实时更新配置的原理已经讲完了,接下来总结服务端的原理:
首先ConfigController下有一个监听器相关的接口,是客户端发起数据监听的接口,主要做两件事:获取客户端需要监听的可能发生变化的配置,并计算其MD5值。执行长轮询的请求。
将长轮询请求封装成ClientLongPolling,交给线程池去执行。
执行过程中,可以理解为一个配置为一个长轮询请求,也就对应一个ClientLongPolling,将其放在一个队列allSubs当中,并且任务总共有29.5秒的等待时间。
如果某一个配置发生改变,会调用LongPollingService的onEvent()方法来通知客户端服务端的数据已经发生了变更。
这里所谓的通知也就是从队列中找到变更配置对应的ClientLongPolling对象,将发生变更的groupKey通过该ClientLongPolling写入到响应对象response中。
那么客户端收到了respones后,自然可以得到更改配置的groupKey,然后去Nacos上查询即可。
本篇文章先是讲了客户端方面如何执行远程配置的加载,再从加载的具体实现细节来一一详解。而这个实现细节也就包括了Nacos-Config如何实现配置的实时更新。
其实讲到这里已经是写完了(累🤣),但是我还是挺怕大家看到这里还是不理解,也怕自己水平有限导致写的文章并不是很好,因此还是以自己的理解整了这么一个流程图给大家:
流程图我真的尽力画了,只是做一个参考,建议以文章的内容为主(重点于小总结),也希望能够帮到大家理解。
关于Nacos具体的加载流程、事件的监听、长连接的处理我就不通过Debug来展示了。感兴趣的小伙伴可以自己Debug下,去官网把Nacos相关的代码下载下来,远程Remote下打个断点试试。
最后,非常感谢所有能够读到这里的读者!(写的可能不是很好,若哪里写的不对,还望大家指出,我及时更正)
到此这篇nacos配置中心原理(nacos配置中心springboot)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/rfx/44821.html