Nacos 配置中心原理解析
Nacos 配置中心的主要作用
本来我寻思把这篇文章放到负载均衡那个讲的,结构感觉那样会很长,我感觉没有人想读论文那样长的技术博客,就算了
在分布式系统中,配置管理面临三大核心挑战:配置分散(多服务、多环境配置散落在各节点)、更新繁琐(修改配置需重启服务)、一致性难保证(多节点配置易出现不一致)。Nacos 配置中心的核心作用就是解决这些问题
- 配置集中化管理
将所有服务的配置(如数据库连接、接口地址、限流规则等)集中存储在 Nacos
Server,替代传统的本地配置文件(如
application.yml
),避免配置分散在各服务节点的文件系统中,便于统一维护。 - 动态配置更新 支持配置在运行时动态修改并生效,无需重启服务。例如:修改数据库连接池参数、调整日志级别、更新限流阈值等,通过 Nacos 推送机制实时同步到所有相关服务,极大提升系统灵活性。
- 多维度配置隔离 通过「命名空间(Namespace)+ 分组(Group)+ Data ID」三级结构,实现多环境(如 dev/test/prod)、多业务(如支付 / 订单)、多版本配置的隔离,避免配置混乱。
- 配置高可用保障 基于集群部署和数据持久化,确保配置不丢失且服务稳定。即使部分 Nacos 节点故障,仍能通过集群同步机制保证配置可用;客户端本地缓存机制也能在 Nacos 服务不可用时,保障应用正常启动。
- 配置版本管理与回溯 记录配置的历史修改记录,支持版本回滚。当配置变更引发问题时,可快速回退到之前的稳定版本,降低变更风险。
所以 Nacos 作为配置中心,起到微服务架构中配置管理的核心组件,其核心价值在于解决分布式系统中配置的动态化、集中化、高可用管理问题。
说白了,Nacos Config 针对配置的管理提供了5种操作:
- 获取配置,从Nacos Config Server中读取配置。
- 监听配置:订阅感兴趣的配置,当配置发生变化的时候可以收到一个事件。
- 发布配置:将配置保存到Nacos Config Server中。
- 动态更新配置:当配置发生变化的时候可以及时变更配置
- 删除配置:删除配置中心的指定配置。
而从原理层面来看,可以归类为两种类型:配置的CRUD和配置的动态监听。
Nacos 配置中心的实现原理
配置的管理,存储和发布
对于Nacos Config来说,主要是提供了配置的集中式管理功能,然后对外提供CRUD的访问接口使得应用系统可以完成配置的基本操作。
- 对于服务端来说:需要考虑的是配置如何存储,是否需要持久化。
- 对于客户端来说:需要考虑的是通过接口从服务器查询得到相应的数据然后返回。
之前说过Nacos的服务发现与注册都是CS架构,而Nacos 配置中心也采用 客户端 - 服务端 架构:
- 服务端(Nacos Server):负责配置的存储、管理、变更通知和集群同步,可集群部署。
- 客户端(Nacos Client):嵌入到应用中,负责拉取配置、监听配置变更、更新本地配置
关系如下

你新建了或者修改了配置,还没有通知到其他服务的之前的时候,需要经过Nacos的配置管理然后再进行配置发布
配置发布的流程大致如下:(客户端 / 控制台 → Nacos Server)
操作发起:
用户(一般服务不会主动更改配置)通过 Nacos 控制台、API 或 SDK 向 Nacos Server 发布配置(如
Data ID
、Group
、配置内容
、配置格式
等)。Data ID
:配置的唯一标识,通常以「应用名 + 环境 + 文件后缀」命名(如user-service-dev.yaml
)。
服务端持久化:
Nacos Server 接收配置后,将其存储到本地文件系统和数据库(默认嵌入式数据库 Derby,可配置 MySQL 实现持久化,这也就是为什么 Nacos 在安装之后要打开配置文件编辑子节点mysql),确保服务再关闭的时候,配置相关的数据不会丢失。
- 本地文件系统:作为缓存,路径为
nacos/data/config-data/{namespace}/{group}/{dataId}
,用于快速读取。 - 数据库:用于持久化存储,支持集群环境下的配置同步。
- 集群数据的同步涉及到 Raft 协议来实现集群数据一致性。Leader 节点负责处理配置更新请求,Follower 节点通过同步 Leader 的日志实现数据一致,确保集群中所有节点的配置状态相同。这里只简单说一下。
- 本地文件系统:作为缓存,路径为
配置的获取(客户端 → Nacos Server)

上面说了,对于客户端来说,需要考虑的是通过接口从服务器查询得到相应的数据然后返回。
配置被发布,此时服务端已经保存下来了之前用户发布到服务端的配置,所以在服务启动时,客户端(Nacos SDK)会向 Nacos Server 拉取指定的配置,流程如下:
- 客户端初始化:应用启动时,通过 Nacos SDK 配置
Data ID
、Group
、Server 地址
等信息。 - 拉取配置请求:客户端向 Nacos Server 发送 HTTP
请求,携带
Data ID
、Group
和配置的 MD5 哈希值(首次拉取时为 null)。 - 服务端响应:Nacos Server 根据 Data ID 和 Group
查询配置:
- 若为首次拉取(无 MD5),直接返回完整配置内容和对应的 MD5。
- 若已有 MD5,对比服务端配置的 MD5:若一致,返回空(无需更新);若不一致,返回新配置和新 MD5。
- 客户端加载:客户端将拉取的配置加载到内存(如 Spring
应用会将配置注入
Environment
),供业务逻辑使用。
配置的动态监听
大家知道上面其实都是 Nacos 的普通功能,其中 Nacos 有一个功能就是能够在你运行的时候更改配置,对应涉及到的服务就会监听到然后自动修改,Nacos 学长这招太狠了
这种配置的动态监听就是 Nacos 的客户端和服务端之间存在的数据交互的一种行为(不然怎么做到实时的更新和数据的查询呢),而对于这种交互行为共有两种方式:
- Pull模式:表示客户端从服务端主动拉取数据。
- Pull模式下,客户端需要定时从服务端拉取一次数据,由于定时带来的时间间隔,因此不能保证数据的实时性,并且在服务端配置长时间不更新的情况下,客户端的定时任务会做一些无效的Pull操作。
- Push模式:服务端主动把数据推送到客户端
- Push模式下,服务端需要维持与客户端的长连接,并且为了检测连接的有效性,还需要心跳机制来维持每个连接的状态。
Nacos采用的是 Pull 模式(Kafka也是如此),并且采用了一种长轮询机制,这招长轮询机制是指客户端发起轮询请求后,服务端如果有配置发生变更,就直接返回。客户端采用长轮询的方式定时的发起 Pull 请求,去检查服务端配置信息是否发生了变更,如果发生了变更,那么客户端会根据变更的数据获得最新的配置。
也就是说,长轮询就是在客户端发起Pull请求后,如果发现服务端的配置和客户端的配置是保持一致的,那么服务端会一直保持住这个请求。(服务端拿到这个连接后在指定的时间段内不会返回结果,直到这段时间内的配置发生变化),一旦配置发生了变化,服务端会把原来保持住的请求进行返回。

所以 Nacos 为实现配置动态更新,客户端会与 Nacos Server 建立长连接监听机制,避免频繁轮询,流程如下:
建立长连接:
配置拉取成功后,客户端通过 HTTP 长轮询 或者 2.x 新版本天生支持的 gRPC 协议来进行长轮询维持,用来向 Nacos Server 注册监听,请求中会携带需监听的
Data ID
、Group
和当前配置的 MD5服务端等待变更:Nacos Server 收到监听请求后,不会立即响应,会检查配置是否发生了变更,然后将请求放入一个阻塞队列,等待配置发生变更:
若在 30 秒内(默认超时时间,可通过
nacos.config.long-poll.timeout
配置)配置发生变更,那么触发一个事件机制,监听到该事件的任务会遍历 allSubs 队列,找到发生变更的配置项对应的 ClientLongPolling 任务,将变更的数据通过该任务中的连接进行返回,即完成了一次推送操作。也就是此时服务端会立即通知客户端并且返回变更的配置信息。若 30 秒内无变更,服务端返回空响应,客户端收到后重新发起长轮询(维持监听)。
客户端处理变更:当客户端收到配置变更通知后,会重新拉取最新配置,并触发本地配置更新逻辑(如 Spring 的 @RefreshScope 注解可实现 Bean 动态刷新)。

配置的动态更新
之后,动态监听到配置一旦发生到更改,会带着新的配置来返回,此时就会涉及到一个配置点动态更新,Nacos Server 会经历「合法性校验→数据持久化→变更记录→MD5 生成」的完整流程
- 更新触发:用户在控制台修改配置并发布,请求会携带
Data ID
、Group
、Namespace
、新配置内容等参数发送到 Nacos Server。Nacos Server 接收更新请求,更新本地文件和数据库中的配置,并生成新的 MD5。- 服务端首先进行合法性校验,然后进行数据的持久化和缓存的更新,然后生成新的 MD5 与变更的通知标记,这样,一条完整的轮回,就此开始与结束
- 通知监听客户端:Nacos Server
遍历阻塞队列中监听该配置的客户端,主动唤醒对应的长轮询请求,返回新配置的
MD5 或内容。
- 客户端启动时,会通过
addListener
方法向 Nacos Server 注册配置监听,携带参数 - 服务端的阻塞队列由专门的线程池(
ConfigExecutor
)管理,核心逻辑是等待配置变更,若变更则立即唤醒,否则超时唤醒,这里源码再说 - 然后如果是集群,集群用 Raft 协议同步一下
- 客户端启动时,会通过
- 客户端同步更新:客户端收到通知后,重新拉取最新配置,覆盖本地缓存,并触发业务逻辑的配置刷新。
- 客户端收到通知(包含新 MD5)后,会立即向 Nacos Server
发送「获取最新配置」的请求,携带
Data ID
、Group
和新 MD5(用于服务端校验)。服务端返回完整的新配置内容,客户端接收后进行格式解析 - 然后就是更新本地存储与缓存
- 最后的最后,最重要的一点,这些新修改的配置如何生效呢,可能会这样
- 出现事件监听:客户端发布配置变更事件(如
ConfigChangeEvent
),业务代码通过注册监听器(onChange
方法)接收事件,执行自定义逻辑(如重新初始化连接池、更新缓存策略)。 - 框架自动刷新:若集成 Spring Cloud,可通过
@RefreshScope
注解标记需要动态刷新的 Bean,框架会在配置变更时销毁旧 Bean 并重新创建,注入新配置。
- 出现事件监听:客户端发布配置变更事件(如
- 客户端收到通知(包含新 MD5)后,会立即向 Nacos Server
发送「获取最新配置」的请求,携带
从源码看 Nacos 配置中心的工作实现
Spring Boot 启动中自动配置的原理过渡到 Nacos
首先需要了解到,Spring Cloud 是基于 Spring Boot 来扩展的,而 Spring
本身就提供了Environment
,用来表示 Spring
应用程序的环境配置(包括外部环境),并且提供了统一访问的方法getProperty(String key)
来获取配置。
对于SpringCloud而言,要实现统一配置管理并且动态的刷新配置,需要解决两个问题:
- 如何将远程服务器上的配置(Nacos Config Server)加载到Environment上。
- 配置变更时,如何将新的配置更新到Environment中。
对于配置的加载而言,需要牵扯到SpringBoot的自动装配,进行环境的准备工作:
还记得之前,在 Spring Boot 讲解主类的源代码的时候,run方法会进行环境的准备工作,其中都是涉及到环境的准备,封装,和上下文的准备和刷新等内容

重点来看this.prepareEnvironment(listeners, applicationArguments)
这个方法:
1 | ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments); |

这是准备环境(prepareEnvironment
)的逻辑,主要负责初始化和配置
ConfigurableEnvironment
,并触发环境准备完成的事件。其中,主要会发布一个ApplicationEnvironmentPreparedEvent
事件,通知所有监听器环境已准备好。然后将环境绑定到
SpringApplication
实例。而BootstrapApplicationListener
监听器会监听这一类的事件,并作出响应的处理,这些内容都是在
监听事件后的处理,进行了自动装配:

1 | // 当 ApplicationEnvironmentPreparedEvent 事件触发时执行 |
而bootstrapServiceContext()
方法则实现了,自动装配BootstrapImportSelectorConfiguration
并且完成了上下文的配置和准备工作
1 | builder.sources(new Class[]{BootstrapImportSelectorConfiguration.class}); |
而自动装配类BootstrapImportSelectorConfiguration
向
Spring 容器中导入BootstrapImportSelector
,这个类在之前讲
Spring Boot 的源码中我们说过,它负责自动配置信息的加载。把 Nacos
各个部分的 spring.factories 注册了进去。
BootstrapImportSelector
通常是一个实现了ImportSelector
接口的类,ImportSelector
接口的核心方法是selectImports
,它会返回需要导入到 Spring
容器中的类的全限定名数组。也就是说,BootstrapImportSelector
会根据一定的条件和逻辑,决定哪些类应该被导入到
Spring
容器中,这些类往往是与应用启动初期的配置、引导等相关的,比如导入一些配置处理器、监听器等,以完善
Spring 应用的初始化配置。
1 | import org.springframework.context.annotation.Configuration; |
在 Spring Cloud 应用启动时,需要先加载一些基础的配置,比如 Nacos
配置中心的地址、命名空间等信息。BootstrapImportSelector
可能会在应用启动的早期阶段,负责导入与
Nacos
配置中心相关的配置类、处理器或者监听器。在我们配置中心这块,这些配置类就在如下位置

其中的 spring.factroies 会自动装配如下类

其中最重要的就是NacosConfigBootstrapConfiguration
1 | // |
而NacosConfigBootstrapConfiguration
也是 Nacos
配置中心在 Spring Cloud 环境中的核心启动配置类,主要作用是初始化
Nacos 配置中心的核心组件,其中
nacosConfigProperties()
创建NacosConfigProperties
实例,用于封装 Nacos 配置中心的核心配置参数,这些参数可以通过 Spring Boot 的配置文件(进行配置,NacosConfigProperties
会自动绑定这些配置。创建 NacosConfigManager 实例,它是 Nacos 配置中心的核心管理器,负责:
- 初始化 Nacos 客户端(
ConfigService
),建立与 Nacos 服务器的连接。 - 提供配置的获取、监听、发布等核心操作的入口。
- 依赖
NacosConfigProperties
中的配置参数来初始化客户端,是后续所有配置操作的基础。
- 初始化 Nacos 客户端(
nacosPropertySourceLocator(NacosConfigManager)
创建NacosPropertySourceLocator
实例,它是 Spring Cloud 中配置源定位器的实现。- 作用是:在 Spring 容器启动阶段(bootstrap 阶段),通过
NacosConfigManager
从 Nacos 服务器拉取配置,并将这些配置封装为 Spring 的PropertySource
加入到环境变量中。 - 这样一来,应用中通过
@Value
或@ConfigurationProperties
注解就能直接获取 Nacos 中的配置,与本地配置无缝融合。
- 作用是:在 Spring 容器启动阶段(bootstrap 阶段),通过
最重要的是,
smartConfigurationPropertiesRebinder(ConfigurationPropertiesBeans)
创建SmartConfigurationPropertiesRebinder
实例(继承自 Spring Cloud 的ConfigurationPropertiesRebinder
)。- 作用是:当 Nacos 配置中心的配置发生动态更新时,自动触发 Spring 中
@ConfigurationProperties
注解标记的 Bean 的重新绑定,确保这些 Bean 能及时获取最新配置,实现配置的动态刷新
- 作用是:当 Nacos 配置中心的配置发生动态更新时,自动触发 Spring 中
这个配置类让大家窥探了Nacos 配置中心工作的基本流程,所以我就详细的说了一下
还有一个这个类也会被自动加载PropertySourceBootstrapConfiguration

可以看到这是一个启动配置类,下面的 initialize()
方法就是会调用PropertySourceLocators.locate()
来获取远程配置信息,PropertySourceBootstrapConfiguration
这个类是
Spring Cloud 中负责处理外部配置源(在这里就是 Nacos
配置中心)的核心启动配置类。
1 | /** |
到这里,你启动 Nacos ,Nacos 环境准备的最核心配置已经完成了最基本该做的了,接下里就要进行相关配置的加载了,继续从run方法出发,来到上下文准备的位置

this.prepareContext()
方法主要是进行应用上下文的一个准备,这个方法的更多内容可以看我的关于
Spring Boot 主类的解析,说的还是比较清楚的关于刷新和上下文准备)))

准备的这个监听器listeners.contextPrepared(context);
,就是监听上下文什么时候完工,然后关闭bootstrapContext
对上下文初始化的方法,就藏在这里this.applyInitializers(context);
,其他我还圈上了一些上下文的bean工厂和神秘的懒加载),我们深入这个方法

你会发现我草给我干哪来了,回来了我去,都联系上了,接到了是上面我们说的initialize()
方法吗,很可惜不是,但是不是完全不是,而是ApplicationContextInitilaizer
的,作用是在应用程序上下文初始化的时候做一些额外的操作,但是最终代码的实现肯定是要跑其子类的代码

而你会发现,PropertySourceBootstrapConfiguration
实现了
ApplicationContextInitializer
和
ApplicationListener<ContextRefreshedEvent>
接口,所以它肯定能在应用上下文初始化和刷新阶段加入配置。因此上面的方法在执行时,会执行PropertySourceBootstrapConfiguration
的initialize()
方法,爆回来了

感觉说的有些含糊,为什么
PropertySourceBootstrapConfiguration
这个类实现了ApplicationListener<ContextRefreshedEvent>, ApplicationContextInitializer<ConfigurableApplicationContext>
这些内容就能完成加载配置的作用,来看其中的这行代码
1 |
|
PropertySourceLocator
接口的主要作用就是实现应用外部化配置可动态加载,而NacosPropertySourceLocator
实现了该接口。因此最终会调用NacosPropertySourceLocator
中的locate()
方法,就像PropertySourceBootstrapConfiguration
类中的
initialize()
方法就是会调用PropertySourceLocators.locate()
一样,实现把Nacos服务上的代码进行加载。
回去看上面PropertySourceBootstrapConfiguration
的代码
1 | public void initialize(ConfigurableApplicationContext applicationContext) { |
这不initialize()
方法吗,是的该方法是
ApplicationContextInitializer
接口的实现方法,在应用上下文初始化时被调用。其中的
doInitialize
类
1 | // 处理每个配置源 |
PropertySourceLocator
列表:通过@Autowired
自动注入所有实现了PropertySourceLocator
接口的 Bean,NacosPropertySourceLocator
就是其中之一,它负责从 Nacos 服务器获取配置。怎么获取的呢,就是调用每个locator
的locateCollection
方法,获取从对应配置源(如 Nacos)加载的配置属性源集合。PropertySourceBootstrapProperties
:封装了与配置源引导相关的配置属性,例如是否在上下文刷新时初始化等。
然后这些内容会被包装到将处理后的配置源添加到 composite
列表中,并记录日志,composite
列表会被进一步处理,将这些配置源添加到应用上下文的环境中,使得应用能够获取到从外部配置源加载的配置信息,就这块

Nacos 配置中心内部的具体实现细节
终于,我们从 Spring Boot 的自动配置原理,无缝过渡到了 Nacos
配置中心能够对配置进行管理的内容,接下里就是正题了。我们先来到上面提到的
NacosPropertySourceLocator.locate()
方法

个方法是 Nacos 配置中心与 Spring 应用集成的关键,负责从 Nacos
服务器加载配置并封装为 Spring 的
PropertySource
。最终得到配置中心上的配置并通过对象封装来返回
其中在这里配置了环境变量,这也就是为什么 Nacos 的配置属性能可以直接读取环境变量
1 | this.nacosConfigProperties.setEnvironment(env); |
在这里获取 Nacos 配置服务
1 | ConfigService configService = this.nacosConfigManager.getConfigService(); |
- 通过
nacosConfigManager
获取ConfigService
实例,这是 Nacos 配置客户端的核心 API。
在这里,就有配置源的加载了,而且是依次加载共享配置、扩展配置和应用自身配置顺序进行加载,所以 Nacos 有默认配置,你可以导入配置,各种配置都能自定义配置的基础,最后返回聚合后的复合配置源。
1 | CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME); |
到这里,配置的加载流程也就完成了,对于具体加载的内容的机制,因为一般来说我们都是根据应用名称来获取配置,所以这里以loadApplicationConfiguration()
方法为例来说。
1 | private void loadApplicationConfiguration(CompositePropertySource compositePropertySource, String dataIdPrefix, |
这是加载了你的服务项目代码里存在的本身的配置,根据
dataIdPrefix
和 fileExtension
构建
dataId
,负责按照 Nacos
的配置命名规则,加载应用的基础配置和多环境配置。也就是,按名加载。
它会先加载基础配置(不带环境后缀),先不带扩展名的配置(如application
),然后带标准扩展名的配置(如application.yaml
),然后遍历所有激活的环境(profiles),加载对应环境的配置,所有配置通过
loadNacosDataIfPresent
方法实际加载,若 Nacos
服务器存在对应配置,则添加到复合配置源中
不断进入方法,loadApplicationConfiguration()—>loadNacosDataIfPresent()—>NacosPropertySource.loadNacosPropertySource() —>NacosPropertySource.build()—>loadNacosData()

来到

到这一步我们只需了解到,加载的具体操作是交给ConfigService
(当然,它是个接口,具体实现交给NacosConfigService
来完成)来加载配置的。
Nacos 事件订阅机制在源码中的实现
接下来主要开始说明 NacosConfig 的事件订阅机制的实现在源码中的体现

SpringBoot在启动的时候,会执行准备上下文的这么一个操作。而Nacos有一个类叫做NacosContextRefresher
,它实现了ApplicationListener
,即他是一个监听器,负责监听准备上下文的事件

看onApplicationEvent
的this.ready.compareAndSet(false, true)
,这个就是这个事件监听器的相应方法,来看看它触发的方法的内容,这个方法主要用来实现
Nacos 事件监听的注册
1 | private void registerNacosListenersForApplications() { |
其中有着事件刷新的监听器registerNacosListener
,这段代码是
Nacos
配置中心实现配置动态刷新的核心逻辑,通过注册监听器实现配置变更时自动通知应用更新。
1 | // 为指定的 Nacos 配置(由 groupKey 和 dataKey 唯一标识)注册监听器。 |
说多了,赶紧往回调整,来看看NacosConfigService
这个类,是
Nacos 配置中心的客户端核心实现类,它是 Nacos
配置中心与应用交互的主要入口,实现了 ConfigService
接口,提供了配置的获取、发布、监听等,真正的核心功能在这
1 | public class NacosConfigService implements ConfigService { |
说一些重要的属性和构造方法里一些比较重要的内容,在考虑这些要单独说吗
- ClientWorker:客户端工作器,负责与 Nacos 服务器通信,处理配置的拉取和推送。
- ConfigFilterChainManager:配置过滤器链管理器,用于处理配置的过滤和转换。
- ServerListManager:服务器列表管理器,负责维护 Nacos 服务器的地址列表。
配置的获取流程就是如下这些方法
1 | public String getConfig(String dataId, String group, long timeoutMs) throws NacosException { |
而接下来配置了这样的一个监听机制
1 | public void addListener(String dataId, String group, Listener listener) throws NacosException { |
- ClientWorker 负责维护监听器列表,并通过长轮询机制监听配置变更。
- 当配置变更时,会触发监听器的回调方法,实现配置的动态更新。
配置发布流程的代码在这
1 | public boolean publishConfig(String dataId, String group, String content) throws NacosException { |
- 配置过滤器:在发布配置前,通过
ConfigFilterChainManager
应用配置过滤器,实现配置的预处理。
还发现了个健康检查机制在这,这都有健康检查的哦真的牛皮
Nacos 配置监听的核心——ClientWorker
来看看 ClientWorker
类是怎么做到维护监听器列表,并通过长轮询机制监听配置变更的,不瞒我说,这个
ClientWorker 类不是 Nacos
中第一难读的,难度肯定也在前三,所以我没法说太细
先来说一下核心职责,首先,它会维护一个维护监听器列表,并通过长轮询机制与服务端通信,实时感知配置变更。

恐怖的代码量和方法调用量,但是很大部分你去看的时候,一眼就行,核心内容还是通过线程池去建立长轮询连接,检查/更新方法的配置的内容来分入手分析。
同样的,我们来看下其构造函数,ClientWorker
的构造函数是整个机制的起点,主要完成线程池初始化和核心通信组件创建,为后续定时
Pull 请求奠定基础。
1 | public ClientWorker(ConfigFilterChainManager configFilterChainManager, ServerListManager serverListManager, NacosClientProperties properties) throws NacosException { |
Executors.newScheduledThreadPool
方法用于创建一个定长的定时调度线程池。线程池是在ClientWorker
初始化时创建的,用于执行定时任务和配置检查:this.initWorkerThreadCount(properties)
方法确定线程池的线程数量,它会根据配置的参数和默认规则来计算合适的线程数。
线程池ScheduledExecutorService
是 Java
并发包中专门用于执行定时任务和周期性任务的线程池接口,它的核心作用就是通过预定义的线程资源,按指定时间规则(延迟执行、周期性重复)调度任务,避免频繁创建线程的开销。所以说它能执行包括周期性发送
Pull 请求、检查配置变更等任务。这个线程池的内容就不细讲解了。
长轮询的核心逻辑在 ConfigRpcTransportClient
的定时任务中,它负责实际发送 Pull 请求(长轮询)和处理响应。
我们可以看到在 ClientWorker
初始化时,会创建
ConfigRpcTransportClient
实例并调用其 start()
方法,这里涉及到一个很复杂的调用链,我们梳理一下
1 | public ClientWorker(...) throws NacosException { |
ConfigRpcTransportClient
继承自
ConfigTransportClient
,其 start()
方法在父类中定义,核心逻辑是触发初始化:
1 | public void start() throws NacosException { |
父类定义 start()
框架,具体初始化逻辑由子类
ConfigRpcTransportClient
的 startInternal()
实现。而初始化时候, ConfigTransportClient
中
start()
方法明确调用了 startInternal()
。

我们的 ClientWorker
的属性引入了ConfigRpcTransportClient
方法,private final ConfigRpcTransportClient agent;
,然后把ConfigRpcTransportClient
方法写成了ClientWorker
的内部类,继承了ConfigTransportClient

而可以看到在 ConfigRpcTransportClient
类的
startInternal
方法中,启动了一个定时任务:
这就实现了整条完整的调用链,ClientWorker 构造函数
→
new ConfigRpcTransportClient()
(创建实例) →
this.agent.start()
(调用父类方法) → 父类
start()
→ 子类
startInternal()
(执行定时任务逻辑)。
ConfigRpcTransportClient
的 startInternal()
方法是定时 Pull 请求的入口,其核心是启动一个阻塞队列 +
循环任务,实现 “按需触发 + 定时检查” 的 Pull 机制:
1 | public void startInternal() { |
startInternal
方法中,我们可以看到,调用了this.executeConfigListen();
,这是配置检查与更新的核心方法,是长轮询的核心实现,主要职责是负责检查配置是否变更并更新本地缓存,当配置发生变更肯定要通知到各个服务端

其工作流程是遍历本地缓存的配置,筛选需要监听的配置项,并通过
checkListenCache()
向服务端发送 Pull
请求(长轮询),检查是否有变更。配置变更通知类(executeConfigListen
)如下
1 | public void executeConfigListen() throws NacosException { |
本地缓存(cacheMap):
ClientWorker
维护一个cacheMap
,存储客户端已订阅的配置(CacheData
实例),每个CacheData
包含配置的 dataId、group、MD5、监听器列表等信息。筛选逻辑:仅对 “未被丢弃” 且 “与服务端可能不一致” 的配置发送 Pull 请求,减少无效通信。
接着在我们的executeConfigListen()
配置检查与更新方法中,我们可以看到其中的checkListenCache()
方法
这个方法是通过线程池提交任务,向 Nacos 服务器发送长轮询请求,检查配置是否变更,可以说是核心中的核心,代码太长了沃日,我只总结部分重要的内容,剩下的省略了
这个方法是定时 Pull 请求的实际执行者,用于检查监听的配置缓存是否有变更,向 Nacos 服务器发送批量监听请求,然后接收并处理服务器返回的变更配置列表,收到更改,刷新配置项完成操作(异步)
1 | private boolean checkListenCache(Map<String, List<CacheData>> listenCachesMap) throws NacosException { |
长轮询机制:客户端发送的 Pull 请求会在服务端 “挂起” 最多 30 秒(默认超时时间)。若期间配置发生变更,服务端会立即返回变更结果;若超时未变更,服务端返回空响应,客户端则立即发送下一次 Pull 请求,形成 “持续监听”。
批量处理:客户端将多个配置项打包成批量请求发送,减少网络交互次数,提升效率。
变更处理:若服务端返回变更配置,客户端通过
refreshContentAndCheck()
拉取最新配置内容,更新本地CacheData
的 MD5 和值,并触发监听器通知应用。
最后,我们来了解这个类中其他一堆另外作用的方法,忽略过一大堆缓存相关的配置和存储读取检查的内容,不多说,直接来到
配置监听机制 (addListeners
和
addTenantListeners
)
ClientWorker
不仅负责发送 Pull
请求,还维护着配置监听器列表,当配置变更时,通过监听器触发应用层面的更新
1 | public void addListeners(String dataId, String group, List<? extends Listener> listeners) throws NacosException { |
- 当应用添加监听器时,
ClientWorker
会为对应的配置项创建CacheData
并关联监听器,同时通过notifyListenConfig()
立即触发一次 Pull 请求,确保客户端与服务端配置同步。
当 checkListenCache()
检测到配置变更并更新
CacheData
后,CacheData
会通过
checkListenerMd5()
对比配置的 MD5
变化,若不一致则触发相关联监听器的 safeNotifyListener
方法,通知应用配置已更新。
1 | void checkListenerMd5() { |
然后紧跟着的是配置获取流程
1 | public ConfigResponse getServerConfig(String dataId, String group, String tenant, long readTimeout, boolean notify) throws NacosException { |
配置发布就是调用了调用 RPC 客户端的 publishConfig
方法。然后将发生变更了的配置发布到 Nacos 服务器。
1 | public boolean publishConfig(String dataId, String group, String tenant, String appName, String tag, String betaIps, String content, String encryptedDataKey, String casMd5, String type) throws NacosException { |
到这里我们主要讲了,Nacos 如何实现 Nacos 事件监听的注册 ,如何实现配置动态刷新的机制,上下文监听后配置如何进行刷新和通知,接下来,我们就需要讲 Nacos Client 是如何定时发送 pull 请求然后得到动态刷新模块发送的发生更改的配置。
总结一下,Nacos Client 通过以下流程实现配置刷新:
- 初始化:
ClientWorker
创建定时线程池,初始化ConfigRpcTransportClient
作为通信代理。 - 定时任务启动:
ConfigRpcTransportClient
的startInternal()
启动循环任务,每 5 秒(或按需)触发executeConfigListen()
。 - 筛选配置:
executeConfigListen()
遍历本地缓存,筛选需要监听的配置项。 - 发送 Pull 请求:
checkListenCache()
通过线程池向服务端发送长轮询请求,批量检查配置变更。 - 处理变更:若服务端返回变更,更新本地
CacheData
,并通过监听器通知应用。 - 循环监听:无论是否有变更,客户端都会持续发送 Pull 请求,确保实时感知配置变化。
Nacos Client 是如何定时发送 pull 请求实现配置刷新
来找到 Nacos Client 包,其中定时发送 Pull
请求实现配置刷新涉及多个包和类,主要逻辑集中在客户端与服务端交互以及配置监听相关的代码,就比如,上面我们讲的ClientWorker
类就在com.alibaba.nacos.client.config.impl.ClientWorker
,如果你觉得我少东西了,你可以去摸摸这种包
在上述 ClientWorker
类中的构造方法中,我们讲解了主要内容是创建定时调度线程池,创建好线程池后,通过
this.agent.setExecutor(executorService)
将线程池设置到
ConfigRpcTransportClient
中。我们进去这个方法
服务器处理长连接机制
引用自https://blog.csdn.net/Zong_0915/article/details/113089265,并且本篇文章提供了大量帮助,感谢
前面主要是讲了事件的订阅、WorkClient创建出的线程池干了什么、以及长连接的建立,但是这些都是面向客户端的,因此接下来从服务端的角度来看一看长连接的处理机制。
在Nacos-config模块下,controller包下有一个类叫做ConfigController,专门用来实现配置的基本操作,其中有一个/listener接口,是客户端发起数据监听的接口。
1 |
|
主要干两件事:
- 获取客户端需要监听的可能发生变化的配置,并计算其MD5值。
inner.doPollingConfig()
开始执行长轮询的请求。
接下来来看一下处理长轮询的方法doPollingConfig()
:
1 | public String doPollingConfig(HttpServletRequest request, HttpServletResponse response, |
即将客户端的长轮询请求封装成ClientLongPolling
交给scheduler
执行。
ClientLongPolling
同样是一个线程,因此也看他的run()
方法,主要做四件事情:
1 |
|
这里可以这么理解:
- allSubs这个队列和ClientLongPolling之间维持了一种订阅关系,而ClientLongPolling是属于被订阅的角色。
- 那么一旦订阅关系删除后,订阅方就无法对被订阅方进行通知了。
- 服务端直到调度任务的延时时间用完之前,ClientLongPolling都不会有其他的事情可以做,因此这段时间内allSubs队列会处理相关的逻辑。
为了我们在客户端长轮询期间,一旦更改配置,客户端能够立即得到响应数据,因此这个事件的触发肯定需要发生在服务端上。看下ConfigController下的publishConfig()方法
我在这里直接以截图的形式来展示重要的代码逻辑:

到这里,如果我们从Nacos控制台上更新了某个配置项后,这里会调用LongPollingService
的onEvent()
方法:
1 | public LongPollingService() { |
意思就是通过DataChangeTask
这个任务来通知客户端:”服务端的数据已经发生了变更!“,接下来看下这个任务干了什么:
1 | class DataChangeTask implements Runnable { |
总结:
- 为什么更改了配置信息后客户端会立即得到响应?
- 首先每个配置在服务端都封装成一个ClientLongPolling对象。其存储于队列当中。
- 客户端和服务端会建立起一个长连接,并且维持29.5秒的等待时间,这段时间内除非配置发生更改,请求是不会返回的。
- 其次服务端一旦发现配置信息发生更改,在更改了配置信息后,会找到对应的ClientLongPolling任务,并将其更改后的groupKey写入到响应对象中response,进行立刻返回。
- 之所以称之为实时的感知,是因为服务端主动将变更后的数据通过HTTP的response对象写入并且立刻返回。
- 而服务端说白了,就是做了一个定时调度任务,在等待调度任务执行的期间(29.5秒)若发生配置变化,则立刻响应,否则等待30秒去返回配置数据给客户端。
Nacos 源码阅读的总结
总结一下:
Nacos Config 的配置加载过程如下:
- Spring
Boot项目启动,执行
SpringApplication.run()
方法,先对项目所需的环境做出准备 BootstrapApplicationListener
监听器监听到环境准备事件,对需要做自动装配的类进行载入。,导入BootstrapImportSelectorConfiguration
配置类,该配置类引入BootstrapImportSelector
选择器,完成相关的初始化操作。- 环境准备完成后(所需的相关配置类也初始化完成),执行方法
this.prepareContext()
完成上下文信息的准备。 his.prepareContext()
需要对相关的类进行初始化操作。由于PropertySourceBootstrapConfiguration
类实现了ApplicationContextInitializer
接口。因此调用其initialize()
方法,完成初始化操作。- 对于
PropertySourceBootstrapConfiguration
下的初始化操作,需要实现应用外部化配置可动态加载,而NacosPropertySourceLocator
实现了PropertySourceLocator
接口,故执行他的locate()
方法。 - 最终
NacosPropertySourceLocator
的locate()
方法完成从Nacos Config Server上加载配置信息。
接下来开始说Nacos Config实时更新的一个原理:
首先,对于客户端而言,如何感知到服务端配置的变更呢?
- 同样的,当SpringBoot项目启动的时候,会执行”准备上下文“的这么一个事情。
- 此时
NacosContextRefresher
会监听到这个事件,并且注册一个负责监听配置变更回调的监听器registerNacosListener
。通过注册监听器实现配置变更的时候自动通知Nacos registerNacosListener
一旦收到配置变更的回调,则由NacosContextRefresher
调用publishEvent
发布刷新事件- 通过
ConfigService.addListener
向ClientWorker
注册监听器,注册完成后,ClientWorker
会通过notifyListenConfig()
向阻塞队列发送信号,触发一次即时的配置检查(避免错过注册前已发生的变更)。 - 一旦发现服务端配置的变更,那么客户端肯定是要再进行配置的加载(
locate()
)的而其最终通过NacosConfigService.getConfig()
方法来实现,在调用这个方法之前,必定要完成NacosConfigService
的初始化操作。- 根据其中根据
NacosConfigService
的构造函数,其中初始化一个ClientWorker。初始化一个配置的过滤器链 - 初始化
ClientWorker
的过程中,构建了定时调度的线程池executorService
,来进行定时的Pull请求和配置检查的内容 ClientWorker
通过ConfigRpcTransportClient
维护的定时任务,以长轮询方式持续与服务端通信,感知配置变更startInternal()
方法启动的循环任务中,每 5 秒(或按需触发)执行executeConfigListen()
,通过checkListenCache()
向服务端发送批量 Pull 请求。- 服务端返回的变更列表中,
ClientWorker
会对比本地CacheData
的 MD5 与服务端新 MD5,确认配置确实变更。 - 当
checkListenCache()
检测到配置变更后,客户端会通过refreshContentAndCheck()
调用ConfigService.getConfig()
,从服务端获取变更配置的完整内容。 - 本地缓存更新后,
ClientWorker
会通过CacheData.checkListenerMd5()
触发监听器回调,完成应用配置的动态刷新 - 调用监听器的
safeNotifyListener
方法,将新配置内容传递给应用。对于 Spring 应用,这里的监听器由NacosContextRefresher
注册 NacosContextRefresher
调用publishEvent
发布RefreshEvent
事件。- Spring Cloud 的
RefreshEventListener
监听该事件,触发环境变量刷新(ContextRefresher.refresh()
)。 - 刷新过程中,Spring 会重新绑定
@ConfigurationProperties
或@Value
注解的 Bean,使新配置生效。
- 根据其中根据
Nacos 配置中心与 Spring
应用的集成核心在于配置加载与聚合,其核心逻辑集中在
NacosPropertySourceLocator.locate()
方法中。
- 首先,
NacosPropertySourceLocator.locate()
方法负责从 Nacos 服务器获取配置并封装为 Spring 可识别的PropertySource
- 通过
nacosConfigManager
获取ConfigService
(实际实现为NacosConfigService
),作为与 Nacos 服务器交互的核心 API。获取到服务器实例 - 然后创建
CompositePropertySource
用于聚合多来源配置,按顺序加载三类配置(共享配置、扩展配置、应用自身配置),最终返回聚合结果。 - 之后
loadNacosDataIfPresent()
是具体加载单个配置的方法,其核心逻辑依赖ConfigService
完成,最终通过ConfigService.getConfig()
从 Nacos 服务器获取配置内容(支持缓存、动态刷新等机制),并封装为NacosPropertySource
后添加到聚合结果中。
这也就是我目前为止写过最长的一篇文章了
本来我还想写一下 Nacos 的在配置中心上的实际应用,但是好像没啥好写的,这玩意一上手感觉就会用其实
强迫症犯了,不写感觉不完整
Nacos 配置中心的实践
(二编:这里基本废弃,我打算单开一个文章来讲解,因为一梳理,发现东西确实不少,有必要拿出来)
添加如下配置文件,服务消费者也一样
1 | spring: |
简要介绍一下 bootstrap 配置文件,,bootstrap
配置文件是一种特殊的配置文件,其核心作用是在应用上下文初始化的早期阶段加载关键配置,为应用的启动和核心组件(如服务注册发现、配置中心等)的初始化提供必要参数。
一般知道,bootstrap
配置文件(bootstrap.properties
或
bootstrap.yaml
)的加载优先级高于普通的
application
配置文件,会在 Spring
应用上下文初始化的最早期被加载,所以一般微服务的时候用的更多
启动 Nacos,像这样创建配置,Data ID一般都是服务名+文件扩展名

写一个配置的控制器类,方便能查看效果
1 |
|
然后试着访问上面我们配置的接口,可以看到是没问题的,我们接下来进行修改

你直接在spring里面修改和在 Nacos 里面修改都可以,为了体现其配置中心性质))在 Nacos 改

修改之后,再次访问,发现配置更改
多环境切换和共享配置先不演示了
共享配置就是
在Nacos中创建共享配置:
Data ID: common-config.yaml
Group: DEFAULT_GROUP
然后在 bootstrap.yml 中把共享配置加进去
1
2
3
4
5
6
7
8spring:
cloud:
nacos:
config:
shared-configs:
- data-id: common-config.yaml
group: DEFAULT_GROUP
refresh: true
Nacos 的环境隔离
Nacos 中服务存储和数据存储的最外层都是 namespace 的位置,用来做最外层的隔离,Nacos 默认的命名空间是 public
你也可以基于 Group 进行隔离,这就涉及到集群的设置了,一般是一个地域的集群设置成一个分组 Group 隔离
或者把一些业务相关度比较高的服务放在一个 Group 进行数据隔离
设置命名空间

按照如下形式修改你的服务的命名空间,注意 填写的是命名空间的 ID
