
一条配置发布服务端如何做到改一次全网秒级生效配置管理模块源码走读文章目录一条配置发布服务端如何做到改一次全网秒级生效配置管理模块源码走读你在控制台点了发布几百个客户端是怎么秒级收到的请求入口ConfigController 的发布方法发布链路从请求到落库再到通知第一步参数校验第二步落库第三步MD5 计算第四步发布变更事件变更通知长轮询是怎么憋住请求的长轮询的工作原理挂起期间配置变了怎么办为什么是 29.5 秒集群内的配置同步不是靠 Raft一张图带走配置管理模块全链路你在控制台点了发布几百个客户端是怎么秒级收到的场景很常见你在 Nacos 控制台改了一个数据库连接池大小点发布。几秒钟后几百个订阅了这个配置的服务实例全都拿到了新值不用重启。问题来了——这几百个客户端服务端是挨个推送的吗配置改动先落库还是先通知如果推送的一瞬间某个客户端刚好断线它还能拿到新配置吗这篇文章从ConfigController的发布入口开始拆开配置管理模块在服务端的完整链路发布落库 → MD5 计算 → 变更通知 → 长轮询响应 → 客户端拉取。每一步给出类名和方法你可以对着 nacos-config 模块的源码跟。⚠️ 本文基于 Nacos 2.3.0 源码。配置的存储链路 1.x 和 2.x 差异不大都走 MySQL但变更通知机制 2.x 增加了 gRPC 推送通道与 1.x 纯 HTTP 长轮询有区别文中会标注。请求入口ConfigController 的发布方法配置发布的入口是ConfigController.publishConfigRestControllerRequestMapping(Constants.CONFIG_CONTROLLER_PATH)publicclassConfigController{PostMappingpublicBooleanpublishConfig(HttpServletRequestrequest,RequestParam(dataId)StringdataId,RequestParam(group)Stringgroup,RequestParam(content)Stringcontent,...){// 1. 参数校验dataId/group 不能为空ParamUtils.checkParam(dataId,group,...);// 2. 计算内容 MD5// 3. 落库 发布变更事件configOperationService.publishConfig(...);}}配置的三维坐标dataId group namespace唯一定位一条配置。发布时这三个值决定了要覆盖数据库里的哪一行。发布链路从请求到落库再到通知一次配置发布服务端走了 4 步事件中心持久化服务ConfigOperationServiceConfigController控制台/API事件中心持久化服务ConfigOperationServiceConfigController控制台/API异步通知本节点长轮询 广播其他节点publishConfig(dataId, group, content)publishConfig(form, request)insertOrUpdate(config_info 表)计算并存储 MD5发布 ConfigDataChangeEvent200 OK发布成功第一步参数校验ParamUtils.checkParam校验 dataId、group 的合法性content 不能超过默认 100KB可配置。第二步落库ConfigOperationService调用持久化服务把配置写入config_info表。这里是insert or update——同一个 dataIdgroupnamespace 存在就更新不存在就插入// 核心持久化逻辑publicvoidinsertOrUpdateConfig(ConfigInfoconfigInfo,...){try{addConfigInfo(configInfo);// 尝试插入}catch(DuplicateKeyExceptione){updateConfigInfo(configInfo);// 主键冲突则更新}// 同时写入 his_config_info 历史表支持回滚}注意每次发布都会往his_config_info历史表写一条记录这是配置回滚功能的数据基础。第三步MD5 计算配置内容落库时服务端会计算内容的 MD5 值并一起存储。这个 MD5 是整个变更检测机制的核心——客户端和服务端都靠比对 MD5 来判断配置是否变化而不是每次都传输完整内容。第四步发布变更事件落库成功后发布ConfigDataChangeEvent。这个事件触发两件事通知本节点上正在长轮询的客户端以及广播给集群其他节点。变更通知长轮询是怎么憋住请求的配置中心的精髓在于变更推送。Nacos 用的是长轮询Long Polling它既不是纯轮询也不是纯推送而是一种折中。长轮询的工作原理有变化无变化有无客户端发起长轮询请求携带各配置的 MD5服务端 LongPollingServiceMD5 有变化?立即返回变更的 dataId挂起请求不立即响应最长 29.5 秒29.5s 内有变更?提前唤醒返回变更超时返回空客户端立即发起下一轮关键在于服务端的LongPollingService客户端请求进来后如果配置没变化服务端不立即返回而是把这个请求挂起利用 Servlet 3.0 的AsyncContext最长挂 29.5 秒。// LongPollingService挂起客户端请求publicvoidaddLongPollingClient(HttpServletRequestreq,HttpServletResponsersp,MapString,StringclientMd5Map,intprobeRequestSize){// timeout 默认 30s减去 500ms 给客户端留余量longtimeout...-500L;AsyncContextasyncContextreq.startAsync();// 把请求包装成 ClientLongPolling 任务放进调度器scheduler.execute(newClientLongPolling(asyncContext,clientMd5Map,...));}挂起期间配置变了怎么办这就是ConfigDataChangeEvent的作用。事件触发后LongPollingService会遍历所有挂起的长轮询请求找到订阅了这个 dataId 的客户端提前唤醒它们立即返回变更通知// 监听配置变更事件唤醒相关的长轮询classDataChangeTaskimplementsRunnable{publicvoidrun(){for(ClientLongPollingclientPolling:allSubClients){if(clientPolling.clientMd5Map.containsKey(changedDataId)){// 提前响应通知客户端这个配置变了clientPolling.sendResponse(Arrays.asList(changedDataId));}}}}为什么是 29.5 秒时间点谁的动作原因30s客户端超时时间客户端设定的请求超时29.5s服务端主动返回提前 500ms 返回避免客户端先超时导致请求浪费 29.5s配置变更提前唤醒有变更时立即响应实现准实时长轮询本质是用可控的延迟换低频的连接。相比每秒轮询它大幅减少了无效请求相比服务端主动推送它不需要维护每个客户端的连接状态HTTP 场景下。集群内的配置同步不是靠 Raft一个常见误解Nacos 配置在集群内是靠 Raft 协议同步的。在 2.x 的默认实现里这不完全对。Nacos 配置的持久化底座是MySQL——所有节点连同一个或同一套主从数据库。配置落库后集群内的同步靠的是节点间的变更通知广播让每个节点去数据库刷新自己的内存缓存节点A 收到发布请求写入共享 MySQL向集群所有节点发 ConfigDataChangeEvent节点B 从 DB 重新加载该配置节点C 从 DB 重新加载该配置更新本地内存缓存 磁盘快照每个节点本地维护了配置的内存缓存和磁盘快照dump 文件客户端拉配置时优先读内存不用每次查库。节点收到变更通知后从 DB 拉最新内容更新本地缓存。⚠️ 生产提示如果集群中某节点的内存缓存和 DB 不一致比如通知丢失可以调用/nacos/v1/cs/ops/localCache相关的 dump 接口触发全量刷新无需重启节点。执行前建议确认业务低峰期全量 dump 会有短暂的磁盘 IO 抬升。一张图带走配置管理模块全链路客户端获取通知发布唤醒ConfigController.publishConfigConfigOperationServiceconfig_info 表 his_config_info 历史表ConfigDataChangeEvent本节点 LongPollingService唤醒挂起的客户端广播其他节点刷新内存缓存客户端长轮询携带 MD5服务端比对 MD5有变化立即返回无变化挂起 29.5s发布 → 落库(含历史表) → ConfigDataChangeEvent → 唤醒本节点长轮询 广播集群刷缓存 → 客户端通过 MD5 比对拉到新配置。整个链路的秒级生效靠长轮询的提前唤醒实现。你们线上配置改了不生效最后定位到的原因是什么评论区留数字1namespace 选错环境 2dataId 拼写不一致 3客户端没加监听器 4RefreshScope 没生效 5集群某节点缓存没刷