Nacos 的 Configuration Service(配置服务) 是其核心模块之一,用于实现动态配置管理,支持配置的集中管理、实时推送、版本控制、灰度发布等功能。它广泛应用于微服务架构中,替代传统的 application.properties 或 application.yml 静态配置方式。
本文将深入解析 Nacos Configuration Service 的设计原理,并结合 Nacos 2.0+ 源码,从请求入口到数据存储、监听机制、长轮询推送等进行 底层代码详解。
一、Nacos Configuration Service 核心功能
| 功能 | 说明 |
|---|---|
| 配置管理 | 支持增删改查配置(Data ID + Group + Namespace) |
| 动态刷新 | 配置变更后,自动推送到客户端,无需重启应用 |
| 环境隔离 | 通过 Namespace 实现多环境(dev/test/prod)隔离 |
| 灰度发布 | 支持按客户端 IP 或标签灰度推送配置 |
| 版本管理 | 保留历史版本,支持回滚 |
| 权限控制 | 基于 RBAC 的权限管理(需开启鉴权) |
| 集群同步 | 多节点间配置数据一致性同步 |
二、架构设计原理
1. 整体架构图
+----------------+ +------------------+ +------------------+
| Client SDK |<--->| Nacos Server |<--->| Nacos Server |
| (Spring Cloud | | (Config Module) | | (Config Module) |
| Alibaba, SDK) | +------------------+ +------------------+
+----------------+ | |
v v
+------------------+ +------------------+
| Data Storage | | Data Storage |
| (Derby/MySQL) | | (Derby/MySQL) |
+------------------+ +------------------+
2. 核心组件
| 组件 | 职责 |
|---|---|
| ConfigController | 接收 HTTP 配置请求(发布、查询、监听) |
| ConfigService | 配置服务主逻辑,协调读写与通知 |
| ConfigStorage | 配置持久化层(支持 Derby/MySQL) |
| LongPollingService | 实现长轮询,监听客户端配置变化 |
| AsyncNotifyService | 异步推送配置变更给客户端 |
| DumpService | 启动时从 DB 加载配置到内存 |
| TenantCounterService | 管理命名空间下的配置数量 |
| RaftCore / RaftConsistencyServiceImpl | Raft 协议实现,保证集群一致性(持久化数据) |
⚠️ 注意:Nacos 配置服务 所有数据均使用 Raft 协议保证强一致性,不使用 Distro。
三、关键流程详解
1. 配置发布(Publish)
流程图
Client -> ConfigController -> ConfigService -> Raft -> DB + 内存 -> PushService -> 客户端
源码入口:ConfigController.publishConfig
文件:com.alibaba.nacos.config.server.controller.ConfigController
@PostMapping("/publishConfig")
public Boolean publishConfig(HttpServletRequest request, HttpServletResponse response,
@RequestParam("dataId") String dataId,
@RequestParam("group") String group,
@RequestParam("content") String content) throws NacosException {
String namespaceId = WebUtils.optional(request, "tenant", "");
String tag = WebUtils.optional(request, "tag", "");
// 构建配置项
ConfigItem configItem = new ConfigItem();
configItem.setDataId(dataId);
configItem.setGroup(group);
configItem.setContent(content);
configItem.setNamespaceId(namespaceId);
configItem.setLastModifiedTime(System.currentTimeMillis());
// 发布配置
configService.publishConfig(configItem, tag);
return true;
}
2. ConfigService.publishConfig
文件:com.alibaba.nacos.config.server.service.ConfigService
public void publishConfig(ConfigItem configItem, String tag) throws NacosException {
// 1. 写入 Raft 日志(强一致性)
raftConsistencyService.put(getConfigKey(configItem), configItem);
// 2. 成功后持久化到 DB
persistService.insertOrUpdate(configItem, tag);
// 3. 触发通知(推送给所有监听该配置的客户端)
notifyConfigListeners(configItem);
}
所有写操作必须通过 Raft 协议达成多数派确认,确保数据一致性。
3. Raft 一致性写入(raftConsistencyService.put)
文件:com.alibaba.nacos.consistency.raft.RaftConsistencyServiceImpl
@Override
public void put(String key, Record value) throws NacosException {
// 将操作封装为 Raft Log
LogEntry logEntry = new LogEntry();
logEntry.setData(serialize(value));
logEntry.setTerm(getTerm());
// 提交日志(Raft 协议核心)
boolean success = raftCore.submit(logEntry);
if (!success) {
throw new NacosException(NacosException.SERVER_ERROR, "Raft submit failed");
}
}
raftCore.submit()会广播日志到集群其他节点。- 多数节点确认后,提交日志,触发
onApply()回调。
4. Raft 日志提交后:更新内存 + DB
文件:RaftCore.java
public void onApply(LogEntry entry) {
Record record = deserialize(entry.getData());
String key = record.getKey();
// 更新内存缓存
ConfigCacheService.onChange(key, record.getValue());
// 持久化到数据库(异步)
persistService.asyncInsertOrUpdate(record.getValue());
}
ConfigCacheService是内存中的配置缓存(ConcurrentHashMap)。- 支持快速查询:
ConfigCacheService.getContent(dataId, group, tenant)
5. 配置查询(Get)
文件:ConfigController.getConfig
@GetMapping("/getConfig")
public String getConfig(HttpServletRequest request) {
String dataId = request.getParameter("dataId");
String group = request.getParameter("group");
String tenant = WebUtils.optional(request, "tenant", "");
// 直接从内存缓存中读取
String content = ConfigCacheService.getContent(dataId, group, tenant);
if (content != null) {
return content;
}
// 缓存未命中,从 DB 加载
return persistService.findConfig(dataId, group, tenant);
}
读操作不走 Raft,直接读内存或 DB,性能极高。
四、动态推送机制:长轮询(Long Polling)
Nacos 客户端通过 长轮询(Long Polling) 实现配置变更的“实时”感知。
1. 客户端请求示例
GET /nacos/v1/cs/configs?listen=1&dataId=test&group=DEFAULT_GROUP&tenant=public
- 客户端携带所有监听的配置的
md5值。 - 服务端对比,若有变更,立即返回;否则挂起最多 30 秒。
2. 服务端处理:LongPollingService
文件:com.alibaba.nacos.config.server.service.LongPollingService
public class LongPollingService extends ExecutorFactory implements Runnable {
private final Map<String, AsyncLongPollingTask> clientList = new ConcurrentHashMap<>();
@Override
public void run() {
// 定期检查是否有配置变更
List<String> changedGroups = getChangedGroups();
for (String groupKey : changedGroups) {
// 通知所有监听该 group 的客户端
for (AsyncLongPollingTask task : clientList.values()) {
if (task.isListening(groupKey)) {
task.sendResponse(); // 触发 HTTP 响应
clientList.remove(task.clientId);
}
}
}
}
}
- 启动一个后台线程,每 5 秒检查一次配置变更。
- 使用
GlobalExecutor.scheduleLongPolling()定期执行。
3. AsyncLongPollingTask:异步长连接任务
class AsyncLongPollingTask {
private ServletRequest request;
private ServletResponse response;
private Map<String, String> listenMd5Map; // 客户端监听的配置及其 md5
public void check() {
for (Map.Entry<String, String> entry : listenMd5Map.entrySet()) {
String groupKey = entry.getKey();
String clientMd5 = entry.getValue();
String serverMd5 = ConfigCacheService.getMd5(groupKey);
if (!clientMd5.equals(serverMd5)) {
sendResponse(); // 立即返回变更
return;
}
}
// 未变更,挂起最多 30 秒
request.getAsyncContext().setTimeout(30000);
}
}
当配置变更时,
ConfigService.notifyConfigListeners()会触发LongPollingService检查。
4. 配置变更通知链路
publishConfig()
→ Raft 提交
→ ConfigCacheService.onChange()
→ NotifyService.notifySubscriber()
→ LongPollingService.wakeup()
→ AsyncLongPollingTask.sendResponse()
→ 客户端收到 304 或新配置
五、数据模型与存储结构
1. 配置主表:config_info
| 字段 | 说明 |
|---|---|
data_id | 配置 ID,如 application.yml |
group | 分组,默认 DEFAULT_GROUP |
content | 配置内容 |
md5 | 内容 MD5,用于比对变更 |
tenant_id | 命名空间(Namespace) |
gmt_modified | 修改时间 |
2. 历史表:config_info_beta / config_info_tag / his_config_info
his_config_info:保留历史版本,支持回滚。config_info_beta:灰度发布专用。config_info_tag:标签路由配置。
六、灰度发布(Beta 发布)
支持对指定 IP 的客户端发布配置。
// 发布 Beta 配置
configService.publishConfigBeta(dataId, group, content, "192.168.1.100,192.168.1.101");
- 写入
config_info_beta表。 - 客户端请求时,若 IP 在 Beta 列表中,优先返回 Beta 配置。
七、启动时加载配置(Dump)
Nacos 启动时,从 DB 加载所有配置到内存缓存。
文件:DumpService.java
@PostConstruct
public void dumpAll() {
List<ConfigInfo> configs = persistService.findAllConfigInfo();
for (ConfigInfo config : configs) {
ConfigCacheService.loadFromDb(config); // 加载到内存
}
}
- 使用分页加载,避免 OOM。
- 支持集群模式下从 Leader 节点同步。
八、设计亮点总结
| 特性 | 说明 |
|---|---|
| 强一致性 | 所有写操作通过 Raft 协议保证 CP |
| 高性能读 | 配置常驻内存,读操作 O(1) |
| 实时推送 | 基于 Long Polling + 异步通知,延迟低 |
| 多环境隔离 | Namespace + Group 实现多维度隔离 |
| 可追溯 | 历史版本、操作日志完整保留 |
| 高可用 | 集群部署,自动选主,故障恢复 |
九、与 Eureka / Apollo 对比
| 特性 | Nacos | Eureka | Apollo |
|---|---|---|---|
| 服务发现 | ✅ | ✅ | ❌ |
| 配置管理 | ✅ | ❌ | ✅ |
| 一致性模型 | Raft(CP) | AP | MySQL + 长轮询 |
| 推送机制 | Long Polling | 客户端拉取 | HTTP Long Polling |
| 多环境支持 | ✅ | ❌ | ✅ |
| 灰度发布 | ✅ | ❌ | ✅ |
Nacos = Eureka + Config Server + Apollo 轻量版
十、参考资料
- Nacos 官网:https://nacos.io
- GitHub 源码:https://github.com/alibaba/nacos
- Nacos 架构设计文档:Architecture
- Raft 协议论文:In Search of an Understandable Consensus Algorithm
1万+

被折叠的 条评论
为什么被折叠?



