系列导读这是系列的最后一篇我们把目光投向 Nacos 的另一半心脏——配置中心Config。在前面第 3 篇我们讲过长轮询、灰度发布和动态刷新的原理第 5 篇剖析了 Raft 在配置中心的落地。今天我们会直接打开源码把配置从发布到客户端热加载的整条链路“跑”一遍配置在服务端如何存储、历史版本如何快照、变更事件怎样在集群中传播、客户端的监听回调链是怎么设计的、以及RefreshScope到底如何收到通知并刷新 Bean。读完这一篇你将能够对 Nacos 配置中心的所有核心实现如数家珍。一、配置存储模型config_info 与 his_config_infoNacos 配置中心的数据持久化依赖 MySQL或内置 Derby其核心表结构设计非常简洁但支持了强大的版本管理和回滚功能。1.1 主表 config_info这张表保存当前生效的配置关键字段如下CREATE TABLE config_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, data_id VARCHAR(255), group_id VARCHAR(255), tenant_id VARCHAR(128) DEFAULT , content LONGTEXT, md5 VARCHAR(32), gmt_create DATETIME, gmt_modified DATETIME, src_user VARCHAR(128), -- 发布用户 encrypted_data_key VARCHAR(255), -- 加密密钥 ... );一个配置由data_id group_id tenant_id三元组唯一确定。content存储完整配置文本md5是内容的 MD5 校验值用于客户端快速判断是否需要更新。1.2 历史表 his_config_info每次配置变更旧版本会被移入历史表CREATE TABLE his_config_info ( id BIGINT, data_id VARCHAR(255), group_id VARCHAR(255), tenant_id VARCHAR(128), content LONGTEXT, md5 VARCHAR(32), gmt_create DATETIME, gmt_modified DATETIME, src_user VARCHAR(128), op_type VARCHAR(10), -- INSERT/UPDATE/DELETE/ROLLBACK ... );op_type标识变更类型回滚操作本质上是将历史版本的内容作为新版本再次发布并生成一条op_typeROLLBACK的新记录。源码中ConfigPersistService负责与数据库交互关键方法有insertOrUpdate()插入或更新config_info同时将旧值移入his_config_info。findConfigHistory()查询历史版本列表。rollbackConfig()取历史内容重新发布。1.3 内存缓存数据库是持久化保证但配置的读取不能每次都查库。Nacos 服务端在启动时会全量加载config_info到内存的ConfigCacheService中它是一个ConcurrentHashMapString, CacheItemKey 为data_id group_id tenant_id拼接的字符串Value 为CacheItem包含配置内容、MD5、最后修改时间、以及一个isBeta标记如果存在灰度版本。当配置发生变更数据库更新后ConfigCacheService同步更新内存缓存并触发事件通知客户端。二、一致性快照与持久化Raft 状态机的实现在第 5 篇我们知道了 Raft 协议的核心现在看它在配置中心源码中的具体应用。2.1 Raft 日志存储Nacos 使用 SOFAJRaft 作为 Raft 引擎日志持久化在本地文件系统默认路径$NACOS_HOME/data/raft/。RaftStore负责日志读写基于 JRaft 的SegmentLogStorage。2.2 状态机 ConfigSMRaft 的“应用端”是状态机Nacos 实现为ConfigSMStateMachine核心方法onApply(iter)在 Raft 提交日志后回调Override public void onApply(Iterator iter) { while (iter.hasNext()) { ByteBuffer data iter.next().getData(); ConfigChangePacket packet (ConfigChangePacket) Serializer.deserialize(data); // 1. 持久化到数据库 configPersistService.updateOrInsert(packet); // 2. 更新内存缓存 configCacheService.updateOrInsert(packet); // 3. 发布事件通知其他节点和客户端 NotifyCenter.publishEvent(new ConfigDataChangeEvent(packet.getDataId(), ...)); iter.next(); } }关键步骤configPersistService.updateOrInsert()更新config_info表旧版移入his_config_info。configCacheService.updateOrInsert()更新内存中的CacheItem。NotifyCenter.publishEvent()发布一个ConfigDataChangeEvent驱动通知流程。这样Raft 的强一致性保证了所有节点在应用日志后数据库和内存缓存都与 Leader 完全一致绝不出现脏读。2.3 节点启动时的数据回放当 Nacos 节点重启JRaft 会自动回放所有已提交但可能未应用的状态机日志snapshot log replay确保ConfigSM.onApply()将最新的配置应用到数据库和内存实现崩溃恢复。三、配置变更事件传播从 NotifyCenter 到客户端配置一旦在 Raft 状态机中生效就需要尽快通知所有正在监听的客户端。这一过程围绕事件总线NotifyCenter展开。3.1 服务端事件流NotifyCenter是 Nacos 内部的事件中心基于经典的发布-订阅模式。配置变更事件ConfigDataChangeEvent发布后有几个关键订阅者AsyncNotifyService负责 HTTP 长轮询和 gRPC 长连接的通知。DumpService负责将最新配置定期同步到磁盘快照容灾用。AsyncNotifyService的核心逻辑Override public void onEvent(Event event) { if (event instanceof ConfigDataChangeEvent) { ConfigDataChangeEvent evt (ConfigDataChangeEvent) event; String dataId evt.dataId; String group evt.group; String tenant evt.tenant; // 1. 唤醒所有 HTTP 长轮询挂起的客户端 ListLongPollingClient clients longPollingService.getClients(dataId, group, tenant); for (LongPollingClient client : clients) { client.sendResponse(evt.lastModifiedTime, evt.md5); } // 2. 通过 gRPC 推送 grpcPushService.pushConfigChange(dataId, group, tenant, evt.md5, evt.lastModifiedTime); } }HTTP 长轮询LongPollingClient被唤醒后会生成包含新 MD5 的 HTTP 响应返回给客户端。gRPC 推送通过双向流Connection发送NotifyConfigRequestprotobuf 消息。3.2 集群节点间的事件同步在 Raft 的体系下ConfigDataChangeEvent只在 Leader 的状态机中发布。Follower 节点也通过 Raft 日志应用同样的状态机更新因此它们的ConfigCacheService和数据库与 Leader 同步。同时每个节点可以独立通知连接在自己身上的客户端无需跨节点传输“通知事件”。这就实现了“数据靠 Raft 一致通知靠本地触发”的清晰解耦。四、客户端监听回调链长轮询与 gRPC 的双通道客户端 SDK 监听配置变更有两种实现路径HTTP 长轮询1.x和 gRPC 长连接2.x。下面结合源码看它们如何串起回调。4.1 HTTP 长轮询客户端客户端入口ConfigService.addListener()内部会启动一个ConfigChangeListener线程循环发起长轮询请求。关键类HttpAgent和LongPollingRunnable// 简化逻辑 public void run() { while (!isStop) { ListString changedGroups HttpAgent.httpPost(server /listener, ...); for (String dataId : changedGroups) { // 拉取最新配置 String content HttpAgent.httpGet(/config, dataId, ...); // 更新本地缓存 configCache.put(dataId, content); // 回调 Listener listener.receiveConfigInfo(content); } } }服务端/listener接口处理就是第 3 篇说的长轮询挂起机制代码在ConfigController.listener()-LongPollingService.addLongPollingClient()。4.2 gRPC 客户端2.x 推荐Nacos 2.x 的 gRPC 模式下客户端启动时会与服务器建立一条BiRequestStream双向流。服务端通过GrpcConnection持有这条流。当配置变更事件触发GrpcPushService.pushConfigChange()会构造一个ConfigChangeNotifyRequest消息通过流推送给客户端。客户端侧的NacosConfigGrpcClient有一个StreamObserver收到推送后public void onNext(ConfigChangeNotifyRequest request) { // 拉取最新配置 String content configRpcClient.getConfig(dataId, group, tenant); // 触发监听器 configCache.put(dataId, content); for (Listener listener : listeners) { listener.receiveConfigInfo(content); } }无论是哪种通信方式最终都会调用到业务方注册的Listener.receiveConfigInfo()完成应用内逻辑处理。五、配置热加载RefreshScope 与 Spring Cloud 整合Spring Cloud Alibaba Nacos Config 将 Nacos 的配置变更无缝接入 Spring 生态其中最常用的就是RefreshScope。5.1 NacosPropertySource 注册应用启动时NacosPropertySourceLocator从 Nacos 拉取配置并注册为NacosPropertySource放入 Spring 的Environment中。这使得我们可以用Value注入配置。5.2 配置变更的传播链当 Nacos 客户端收到配置变更无论是长轮询还是 gRPCNacosContextRefresher会被触发NacosConfigService回调业务 Listener。Spring Cloud Alibaba 的NacosConfigAutoConfiguration注册的NacosConfigListener收到配置内容。调用NacosContextRefresher.refresh()。ContextRefresher发布一个RefreshEventRefreshEventListener监听到后销毁所有RefreshScope的 Bean 缓存。当这些 Bean 下次被访问时重新从Environment取值即最新的配置。关键源码在NacosContextRefresherjavapublic void refresh() { // 发布 RefreshEvent applicationContext.publishEvent(new RefreshEvent(this, null, Nacos config changed)); }RefreshScope本身是 Spring Cloud 提供的通用机制Nacos 只是通过事件触发它。5.3 非 Spring 环境对于不使用 Spring 的 Java 应用Nacos 同样支持通过ConfigService.addListener()直接注册Listener在回调中自行解析配置并更新业务变量。这同样能实现“热加载”只是少了自动刷新 Bean 的便利。六、配置模块源码关键类索引为了方便你深入阅读这里给出 Nacos 2.x 配置中心的核心包和类服务端config 模块server.serviceConfigPersistService、ConfigCacheService、LongPollingService、AsyncNotifyServiceserver.service.raftRaftConsistencyServiceImpl、ConfigSM、RaftStoreserver.controllerConfigControllerHTTP APIserver.remote.grpcConfigPushGrpcService、GrpcPushService客户端client 模块configNacosConfigService、HttpAgent、LongPollingRunnableconfig.grpcNacosConfigGrpcClient、ConfigChangeNotifyStreamObserverSpring Cloud Alibaba 整合NacosPropertySourceLocator、NacosContextRefresher、NacosConfigAutoConfiguration你可以从ConfigController.publishConfig()开始追踪一条配置发布如何经过 Raft到通知客户端的全过程。七、全系列总结至此我们完成了对 Nacos 从架构到原理、再到源码的系统性解读。让我们快速回顾这 8 篇的核心旅程篇目主题掌握内容第 1 篇Nacos 架构总览双引擎APCP、数据模型、插件化设计第 2 篇服务注册与发现心跳机制、临时/持久实例、健康检查、自我保护第 3 篇配置中心机制长轮询、灰度发布、动态刷新、RefreshScope 原理第 4 篇Distro 协议一致性哈希、非对称复制、健康检查驱动、版本号第 5 篇Raft 协议落地JRaft 引擎、Leader 选举、日志复制、状态机应用第 6 篇集群与高可用跨机房部署、Nacos-Sync、就近路由、扩缩容第 7 篇服务发现源码注册表结构、哈希环、责任链、gRPC 推送、注册全流程第 8 篇配置中心源码存储模型、Raft 状态机、事件传播、客户端回调、热加载如果你耐心读完了所有文章相信你已经从“会用 Nacos”蜕变为“懂原理、能调优、敢看源码”的进阶开发者。Nacos 不仅仅是一个工具它凝聚了分布式系统设计的诸多经典思想AP 与 CP 的取舍、最终一致性的协议设计、事件驱动的推送架构、高度可插拔的扩展体系。理解这些对于学习其他分布式中间件也大有裨益。本系列到此完结感谢你的阅读。如果对你有帮助请点赞、收藏支持也欢迎将系列分享给更多的朋友。如果有任何问题或希望我更新某些细节请在评论区留言我会尽力回复。