SpringCloud Alibaba Nacos 配置动态更新源码学习总结

众所周知,nacos两大核心功能,服务注册发现与动态配置
支持服务注册发现的有:Eureka、Consul、Zookeeper、Nacos
支持动态配置的有:Spring Cloud Config、Nacos、Apollo、Consul
像支持分布式的框架,必须得借用第三方服务,比如定时任务调度xxl-job,分布式事务seata,都分为server端与client端,client端即众多微服务,server端需要单独部署,好在nacos server端也是java编写,所以在spring项目运用更广泛
这里nacos有springboot的版本,隶属于nacos-group,有SpringCloud的版本,隶属于alibaba,一般也会接入Cloud版本的

SpringCloud Alibab有很多出名的组件,这里先记录下服务配置的一些东西,服务注册的后续有时间再看
首先,还是这两个文件,一个是新版本的自动配置,另一个是其他的配置

相比于AutoConfiguration、BootstrapConfiguration更优化,因为它会引导项目启动,做项目启动前的一些准备
打开NacosConfigBootstrapConfiguration,首先映入眼帘的是NacosConfigProperties,spring.cloud.nacos.config为前缀的配置文件

接下来就是NacosConfigManager,主要作用是createConfigService方法创建ConfigService

可以看到的是一个很古老的单例模式

nacos的配置文件加载完了,下面就是这个NacosPropertySourceLocator,算是比较重要的了,用于定位和获取远程配置,查看其实现,发现Spring Cloud Config也用的这个,主要用于实现locate方法
在locate方法中可以看到Environment当前已经当前已经加载的propertySources,是一个CopyOnWriteList


下一个加载的就是当前nacos的Property,因为nacos配置存储与服务端,所以在这个阶段肯定会发起请求,得到服务端的详细配置信息
NacosPropertySourceBuilder 这个类里面build方法会执行实际的逻辑,CompositePropertySource而这个是spring core的类,用于将nacos的property组装到现有的property

loadApplicationConfiguration开始调用配置,会分为三次依次执行,例如项目名叫demo,文件后缀叫yaml,根据文件后缀第一次获取demo, 第二次根据文件名后缀获取demo.yaml,第三次根据activeProfile获取,如果有多个则会获取多次,比如dev环境,获取demo-dev.yaml

并且根据顺序优先级越高,后面加载的优先级会高于前面加载的,因为CompositePropertySource组装所有的property的时候addFirstPropertySource是一个LinedHashSet,先删除再插,将当前的排在第一位

NacosPropertySourceBuilder的loadNacosData根据上面的dataId以及group调用configService,configService是locate方法中通过nacosConfigManager通过单模式创建出来的,nacosConfigManager又通过NacosFactory工厂模式创建出来,
ConfigFactory使用代理模式最终创建

可以看到创建的是NacosConfigService。

在构造器中执行了不少逻辑,第一步创建http请求,同时在start里面开启了一个定时任务在项目启动成功之前校验serverUrl,第二部创建ClientWorker,主要创建线程池以及创建刷新配置的定时任务,

同时获取serverConfig真正发起请求的方法放在了ClientWorker里面,tenant是上游传过来的namespace,开启调用/v1/cs/configs接口,值得注意的是这里如果请求失败直接会抛出NacosException,而在后面没有捕获这个异常,所以如果nacos的serverAddress获取哪个地址配置错误,项目直接会起不来的,

在NacosPropertySourceBuilder的loadNacosData方法中,获取到了配置的信息,下一步是通过NacosDataParserHandler转换为Linedhashmap,因为MapPropertySource需要的source就是map结构

nacos控制端有text,json,yaml,html,properties,所以在解析的时候可以看到也有对应的几种解析方式,但是刚才http返回的是一个string类型的,如何判断服务端是哪一种类型的,


没法判断类型的话那就一个一个来吧,但是通常我们springboot会使用yaml格式,nacos正常也会使用yaml格式的,所以使用的责任链模式,依次执行,这里可以看到首次执行的是yaml解析器


实际执行解析的逻辑还是借用spring里面的YamlMapFactoryBean,解析完成就是下面这样子的,下一步需要做的就是将多层的map进行摊平为一层

至此,远程配置文件以及请求到本地,并且解析完了,已经转换为NacosPropertySource了,正常该进行组装了,这里为了后续逻辑,放入了NacosPropertySourceRepository缓存

这里nacos的三个property将会通过CompositePropertySource放入Environment中,

具体的组装逻辑在PropertySourceBootstrapConfiguration里面insertPropertySources
因为Environment的propertySource是CopyOnWriteList,并且放在最前面

到这里配置就加载完成了,开始执行spring 加载逻辑了
再次查看Enviornment发现最前面三个是刚才新建的nacos配置

动态更新配置

紧着这后面后扫描org.springframework.boot.autoconfigure.EnableAutoConfiguration
会发现里面有个NacosContextRefresher


仔细一看,这个类大有来头,实现了ApplicationListener,泛型是ApplicationReadyEvent,
spring实现发布订阅模式的事件监听器主要有两种方式,第一是实现ApplicationListener接口并且重写onApplicationEvent,第二种是注解形式@EventListener
而监听的这个事件ApplicationReadyEvent也是spring生命周期很重要的一个扩展点,标志着完成了大多数的准备工作,准备开始处理业务请求,
这个时候Spring容器初始化完毕,所有的bean已经被实例化、配置和初始化完成;AutoCOnfiguration自动配置也已经完成;所有视线CommandLineRunner和ApplicationRunner接口的run方法也已经被执行;通过@PostConstruct以及实现了InitializingBean的afterPropertiesSet方法也已经执行完成。
并且在容器初始化之前已经请求过一次配置,这个时候只需要想办法刷新本地的配置与服务端一直就可以,正好借用ApplicationReadyEvent事件,所有准备工作做完之后开始初始化nacos的监听器,来监听服务端的更新

可以看到registerNacosListenersForApplications方法为当前应用创建了一个nacos的监听器,而NacosPropertySourceRepository这个也是比较熟悉了,在放入CompositePropertySource之前先放入了NacosPropertySourceRepository里面的缓存当中取出NacosPropertySource,最终还是在ClientWorker中注册监听器

值得注意的是addCacheDataIfAbsent方法,CacheData是存放配置信息内容的地方,CacheData会首先获取本地快照文件的内容,这个快照内容是发送http请求完成之后立马将原文放入快照文件当中

一般本地快照文件里面是有内容的,默认也是开启了远程同步的,所以远程同步完会覆盖掉之前的content,并且md5值是根据content重新计算

nacos中的listeners也是一个CopyOnWriteArrayList,所以可以给每个CacheData添加多个监听器,并且使用ManagerListenerWrap,可以看到这是正儿八经的装饰器模式,lastCallMd5表示最后一次配置文件的md5值

到这里监听器创建完了,并且set到CacheData里面了,并且ClientWorker里面的cachaeMap里面就是当前的三个CacheData

所有监听器都创建完了,那么在哪开始执行的呢?
在创建ClientWorker的时候,开启了一个10ms的定时任务,主要方法为checkConfigInfo,一直检测cacheMap的情况,因为上面看了源码, 一旦cacheMap有值表示已经请求过服务端的配置信息,并且监听器已创建完成

为了防止有的项目配置文件太多,这里3000个cacheDate分为一个任务,并且开始执行长轮询任务,也就是nacos动态配置核心的开始

上面的check failover config暂时不用看,只需要看check server config,一直到checkUpdateConfigStr,真正发起长轮询的请求,请求服务端的/v1/cs/configs/listener接口

其中param为分组的任务,根据特殊符号进行分割,将dataId,group,namespace以及md5值传到服务端,如果有3000个,将会拼接3000次,这里的timeout为30000


紧接着查看nacos服务端的代码,

首先会在controller中将拼接的客户端信息进行解析,然后会立马进行一次执行,随后如果有变化的话会 马上返回,但是当没有变化,会开启异步线程,异步线程当中执行了一个延迟任务,而延迟时间默认为客户端传过来的30000-500,也就是29.5s

这里ClientMd5Map是上面用特殊符号组装的客户端goup,以及当前的md5值这些,而ConfigCacheService.isUptodate会取出当前服务端最新配置的md5值,如果比较是不一致的,表示当前服务端的配置发送了更新,因为有多个配置,所以在返回response的时候又利用特殊符号拼接进行返回,而此时,这个延迟任务也会结束,发送响应

而客户端接受到之后发现结果不为空,也就是最新的配置更新了,会根据刚才返回的grupKey进行请求配置信息


紧接着会调用cacheData的checkListenerMd5,如果md5值不想同,会发送监听事件修改本地的配置

最终发布RefreshEvent事件

在ContextRefresh中会重新进行刷新Environment

posted @ 2024-07-03 18:08  木马不是马  阅读(186)  评论(0编辑  收藏  举报