Nacos配置服务的源码解析

从Nacos的源代码分析Nacos Cofnig的实现

NacosConfigAutoConfiguration

负责Nacos配置服务的自动配置

如果spring.cloud.nacos.config.enabled(默认为true)为false时因为ConditionalOnProperty注解不导入到Spring Factory。

装配的Bean:

  • NacosConfigProperties:Nacos配置文件POJO,负责通过Spring的配置功能从配置文件中加载nacos的配置(前缀为spring.cloud.nacos)。创建时会考虑ApplicationContext的层次性。使用方法assembleConfigServiceProperties封装成Properties
  • NacosConfigManager:Nacos配置管理器,接收一个(依赖于)NacosConfigProperties,并且创建(通过NacosFactory.createConfigService)、管理(提供getter)**ConfigServer(配置服务器)**对象。
  • NacosRefreshHistory:Nacos刷新历史,存储Nacos的刷新历史。历史记录时间戳DataIdGroup、以及数据的摘要(MD5)。最多存储最新的MAX_SIZE(20)条记录。用于在Endpoint中展示。
  • NacosContextRefresher:Nacos上下文刷新器,接收(依赖于)NacosConfigManagerNacosRefreshHistory,负责在程序启动时从ConfigServer拉取配置,以及侦听配置的刷新并执行刷新。通过Nacos配置文件的spring.cloud.nacos.config.refresh-enable(默认开启)属性控制开关。当Spring Application发布ApplicationReadyEvent事件后向ConfigServer添加对于所有开启自动刷新的配置源所指定的DataIdGroup的数据监听(Listener)。

Nacos Config实现

NacosPropertySourceLocator

Nacos Config通过使用Spring Cloud提供的PropertySourceLocator进行资源定位。Nacos创建其实现类NacosPropertySourceLocator定位配置服务器上的配置文件。

首先看一下PropertySourceLocator是干什么的。PropertySourceLocator让spring读取我们自定义的配置文件(注册到Spring Environment),然后使用**@Value注解即可读取到配置文件中的属性,这解释了为什么Nacos等配置中心可以直接使用Value注解进行配置的读取。值得一提,这是基于SPI(Service Provider Interface)机制的,需要在META-INF/spring.factories中定义BootstrapConfiguration**,所以Nacos Config也实现了一个BootstrapConfiguration,其中就向Spring容器注册了NacosPropertySourceLocator这个Bean。

PropertySourceLocator中提供了方法locate,方法传入一个Environment对象(当前Spring应用环境),返回一个PropertySource对象(配置源)。在Nacos实现的定位器中实现了从配置服务器加载配置的功能。分析源码得:

  1. NacosConfigManager获取ConfigServer对象,如果为空,返回null。
  2. 获取超时时间spring.cloud.nacos.config.timeout)及配置文件名称spring.cloud.nacos.config.name),如未指定名称则使用前缀(spring.cloud.nacos.config.prefix),如果还是没有指定则使用应用名(spring.application.name)。
  3. 创建CompositePropertySource(表示一组配置源,继承于PropertySource,Name为NACOS)。
  4. 加载共享配置(loadSharedConfiguration
    1. 从配置文件中获取spring.cloud.nacos.config.shared-configs中的配置文件
    2. 通过NacosPropertySourceBuilder构建配置源(如果可刷新的配置源不为0个(代表已加载过配置源)且不开启自动刷新,从配置源仓库(NacosPropertySourceRepository)中加载)
    3. 存入NacosPropertySourceRepository
  5. 加载拓展配置(loadExtConfiguration),类似于加载共享配置
  6. 加载应用配置(loadApplicationConfiguration
    1. 获取配置文件格式(spring.cloud.nacos.config.file-extension
    2. 获取组(spring.cloud.nacos.config.group
    3. 使用步骤2中获取的配置文件名称直接加载一次,存入NacosPropertySourceRepository
    4. 加上文件类型的后缀名加载一次,存入NacosPropertySourceRepository
    5. 对于所有活动的配置文件(Environment.getActiveProfiles)使用文件名-配置文件后缀.文件类型的作为配置名称进行加载,存入NacosPropertySourceRepository
  7. 返回CompositePropertySource

这样就实现了获取并使用配置服务器上的配置文件的功能,而且可以发现Nacos不仅仅加载单个配置文件,它会把所有可能涉及的配置文件都加载进来作为一个配置源。

NacosPropertySourceBuilder

此外关于如何从Config Server获取配置源的流程封装在NacosPropertySourceBuilder,它在NacosPropertySourceLocator进行资源定位(locate)时被创建。

使用这个类的build方法将会从配置服务器加载配置源。

  1. 使用ConfigServergetConfig方法加载配置
  2. 使用NacosDataParserHandlerparseNacosData方法解析数据
    1. 通过PropertySourceLoader进行加载,这里会有多个实现(JSON、XML以及原有的YML、PROPERTITES)
    2. 创建NacosByteArrayResource,并且设置文件名(带后缀)
    3. 调用PropertySourceLoader.loadNacosByteArrayResource读取内容返回一个数据源列表
    4. EnumerablePropertySource转为OriginTrackedMapPropertySource
  3. NacosPropertySourceRepository存入加载的数据源

在这里插入图片描述

NacosFactory

这个类封装了Nacos的几个Factory(ConfigFactoryNamingFactoryNamingMaintainFactory)来提供ConfigServiceNamingServiceNamingMaintainService

ConfigFactory

用于创建ConfigService,只有两个方法分别通过Porperties和serverAddr来创建配置服务。但是其创建配置服务的具体实现在Porperty中(serverAddr被封装为Porperties)。

  1. 通过反射获取了**NacosConfigService(ConfigService的具体实现)**这个类
  2. 调用类的构造方法创建实例(传入Porperties
    1. 检查Properties中CONTEXT_PATH属性是不是合法(不为空且没有两个’/'连在一起)
    2. 从Properties中拿到编码默认为(UTF-8
    3. 从Properties中取出并且构造为命名空间放回Properties(是为了整合多租户和云端解析)
    4. 创建ConfigFilterChainManager对象,这个类实现了IConfigFilterChain接口,管理一组IConfigFilter(按顺序对过滤器排序,内部将执行过滤器链任务委托给一个VirtualFilterChain实现)。创建时使用JDK6引入的ServiceLoader查找IConfigFilter
    5. 创建一个agentMetricsHttpAgent),用于与服务器进行交互(基于HTTP协议),封装了通信细节。
    6. 创建ClientWorker,提供了getServerConfig的实现,用于从配置服务器获取指定配置。

剩下几个是属于Nacos注册中心的工厂,先不做讨论。

NacosConfigService

Nacos的配置服务,提供获取配置(getConfig)、添加监听器(addListener)、移除监听器(removeListener)、发布配置(publishConfig)和移除配置(removeConfig)功能。

在这里插入图片描述
底层都是交由ClientWorker或者Agent实现,只是在其基础上进行封装作为门面。

  1. 获取配置getConfigInner封装worker实现,会依次从本地文件(开发者管理)、服务器和快照(在从服务器获取成功配置后存储)尝试获取配置,然后通过ConfigFilterChainManager进行定制操作(从服务器获取失败时抛出异常)。
  2. 发布配置publishConfigInner封装agent实现,会先交给ConfigFilterChainManager进行定制操作,将要发布的配置内容和基本信息通过agent发送到配置服务器,返回值为是否发布成功,如果服务器返回状态码HTTP_FORBIDDEN则抛出异常。
  3. 异常配置removeConfigInner封装agent实现,将要移除的配置基本信息通过agent发送到配置服务器,返回值为是否删除成功,如果服务器返回状态码HTTP_FORBIDDEN则抛出异常。
  4. 添加监听移除监听全部直接交由worker负责。

ClientWorker

客户端工作器。提供添加监听器、移除监听器、添加缓存数据、获取缓存数据、获取配置服务器配置内容以及后台定时拉取更新配置。

在这里插入图片描述

监听器相关

ClientWorker中关于监听器的实现是围绕CacheDataListener展开的。

Listener

用于接收指定配置的更新。

其中,receiveConfigInfo方法负责接收更新内容、getExecutor用于指定执行通知时的线程池。

AbstractSharedListener用于接收所有共享配置,在通知前会使用fillContext来指明来自哪一个配置。

PropertiesListener将接收到的配置转为Properties再交给用户处理。

AbstractConfigChangeListener是一个特殊的监听器,会另外接收一个ConfigChangeEvent用于指定配置文件变化内容。

结构图如下
在这里插入图片描述

CacheData

这个类代表了本地的一份缓存数据,是配置服务器上某一份配置的本地缓存。存储DataIdGroupTenantNamespace)和Content这几个核心内容。此外还存储了内容的MD5摘要文件类型监听器列表。Nacos的监听器就是配置在这个类中的。
在这里插入图片描述

基本原理是从服务器拉取最新配置后通过setContent方法设置最新内容并且重新计算md5,调用checkListenerMd5方法来对所有的md5和新内容不一致监听器进行更新通知。

  • CacheData中定义了一个ManagerListenerWrap类对Listener进行封装,保存了这个监听器内容的MD5摘要以及最新内容

具体通知方式是在safeNotifyListener中实现的,对于每一个md5和新内容不一致监听器都使用这个方法进行通知。

视Listener的getExecutor方法是不是为空进行同步/异步执行下列操作。

  1. 判断监听器是不是共享数据监听器(AbstractSharedListener)的实例,是则调用fillContext设置属性。(因为共享设置监听器不止监听一个配置)
  2. 构造ConfigResponse,然后通过ConfigFilterChainManager进行定制操作。
  3. 取出处理过的内容,调用监听器的receiveConfigInfo方法进行通知。
  4. 判断监听器是不是AbstractConfigChangeListener的实例,是则通过ConfigChangeHandlerparseChangeData方法获取所有更新的内容(用ManagerListenerWrap的监听器之前最新内容为参照),封装为ConfigChangeEvent,然后调用receiveConfigChange方法进行通知。
  5. 更新ManagerListenerWrap的最新内容。

值得注意的是,创建一个CacheData实例的时候会优先从用户指定配置进行加载,如果不存在则从快照进行加载初始内容与加密秘钥,同时,设置为正在初始化状态,等待LongPollingRunnable进行更新检查完成初始化

配置刷新

ClientWorker管理了一组CacheData,并且在后台进行10ms一次的checkConfigInfo,这个方法主要检查管理的CacheData是否需要被更新。过程如下:

  1. 获取管理的CacheData数量。
  2. 确定长轮询总量(每3000个CacheData对应一个长轮询)。
  3. 如果长轮询总量大于已有长轮询的数量则向线程池放入一个LongPollingRunnable实例,并且更新已有长轮询的数量。
LongPollingRunnable

这个类实现了Runnable接口,负责向服务器进行长轮询,流程如下:

  1. 获取这个任务对应的所有CacheData。

  2. 检查这些CacheData的本地内容。

    1. 如果不使用本地配置本地文件存在:使用本地文件内容填充CacheData,使用本地配置标记。
    2. 如果使用本地配置本地文件不存在:取消使用本地配置的标记。
    3. 如果使用本地配置本地文件存在本地文件版本(最后修改时间)更高:使用本地文件内容填充CacheData,使用本地配置标记。

    【本地文件包括用户配置(高优先级)和快照】

    【本地标记配置在CacheData初始化时默认为 false,每次更新后为 true

  3. 如果使用本地配置文件填充则校对MD5摘要,如果更新了进行通知。(调用CacheData#checkListenerMd5

  4. 检查所有没有使用本地配置的CacheData,组装配置名称,最后汇总成一个名单,如果有初始化的CacheData置isInitializingCacheList标记为true。

    1. 向服务器发起请求,如果isInitializingCacheList为true则即时返回,否则挂起一段时间(默认3s)直到时间结束或者名单中的配置文件有更新。
    2. 如果服务器返回不正常则将setHealthServer(false),表示服务器状态差。
    3. 否则设置为true,并且解析返回内容(需要更新的配置名单)。
  5. 对于要更新的配置名单,使用getServerConfig方法从服务器获取最新配置。

  6. 向对应的CacheData填充数据(内容、秘钥和类型)。

  7. 对于所有更新的CacheData,如果这个CacheData不处于初始化状态或者在更新名单中检查MD5摘要并且设置为已经初始化。

  8. 清理相关内容后重新在线程池中运行自身(循环)。

HttpAgent

Http代理。负责和服务器进行实际交互,屏蔽HTTP细节。不多讲解。
在这里插入图片描述

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐