架构原理
理解 Nacos 的架构设计有助于更好地使用和运维 Nacos。本章深入介绍 Nacos 的核心架构、一致性协议和数据模型。
整体架构
Nacos 采用分层架构设计,将服务发现和配置管理两大功能融合在一个平台上。
逻辑架构图
┌─────────────────────────────────────────────────────────────────────────┐
│ Nacos 逻辑架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ API 层 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ OpenAPI │ │ Console │ │ SDK │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 核心模块层 │ │
│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │
│ │ │ 服务管理 │ │ 配置管理 │ │ 元数据管理 │ │ │
│ │ │ • 服务注册 │ │ • 配置存储 │ │ • 标签管理 │ │ │
│ │ │ • 服务发现 │ │ • 配置推送 │ │ • 数据索引 │ │ │
│ │ │ • 健康检查 │ │ • 版本管理 │ │ │ │ │
│ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 协议与通信层 │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Distro 协议(临时实例) Raft 协议(持久实例/配置) │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ HTTP/REST API │ gRPC(2.x 新增长连接) │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 存储层 │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ 内置存储(Derby) │ 外部存储(MySQL/PostgreSQL) │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
核心组件说明
| 组件 | 功能 |
|---|---|
| OpenAPI | 提供 RESTful 风格的 HTTP 接口,支持多语言集成 |
| Console | Web 控制台,方便进行服务和配置管理 |
| SDK | 多语言客户端 SDK,简化开发集成 |
| 服务管理 | 服务的注册、发现、健康检查、权重管理 |
| 配置管理 | 配置的存储、推送、版本管理、灰度发布 |
| 元数据管理 | 服务和配置的元数据存储、标签管理 |
Nacos 2.x 架构改进
Nacos 2.x 相比 1.x 进行了重大架构升级,主要改进包括:
gRPC 长连接
Nacos 1.x 使用 HTTP 短连接,每次请求都需要建立连接,在高并发场景下性能受限。Nacos 2.x 引入 gRPC 长连接:
┌─────────────────────────────────────────────────────────────────┐
│ Nacos 1.x vs 2.x 连接模式 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Nacos 1.x(HTTP 短连接) Nacos 2.x(gRPC 长连接) │
│ │
│ ┌────────┐ ┌────────┐ │
│ │ Client │ ──── 请求1 ────► │ Client │ ═════════════► │
│ └────────┘ └────────┘ 长连接 │
│ │ │ │
│ │ ◄─── 响应1 ──── │ ◄──── 数据推送 ──── │
│ │ │ │
│ │ ──── 请求2 ────► │ ◄──── 数据推送 ──── │
│ │ │ │
│ │ ◄─── 响应2 ──── │ ... │
│ │ │ │
│ ┌────────┐ ┌────────┐ │
│ │ Server │ │ Server │ │
│ └────────┘ └────────┘ │
│ │
│ 每次请求建立新连接 复用长连接,服务端可主动推送 │
│ 连接开销大 连接开销小,实时性好 │
└─────────────────────────────────────────────────────────────────┘
gRPC 长连接带来的优势:
- 减少连接开销:避免频繁建立和断开连接
- 实时推送:服务端可以主动推送数据,无需客户端轮询
- 性能提升:使用 Protocol Buffers 序列化,传输效率更高
- 资源占用低:单个连接处理多个请求,减少资源占用
端口变化
Nacos 2.x 新增了 gRPC 端口:
| 端口 | 计算方式 | 用途 |
|---|---|---|
| 8848 | 默认主端口 | HTTP API、控制台 |
| 9848 | 8848 + 1000 | 客户端 gRPC 请求端口 |
| 9849 | 8848 + 1001 | 服务端 gRPC 通信端口 |
| 7848 | 8848 - 100 | Raft 协议端口 |
使用 VIP/Nginx 时,需要配置 TCP 转发而非 HTTP 代理,否则会导致 gRPC 连接中断。gRPC 基于 HTTP/2,Nginx 需要支持 HTTP/2 代理。
性能对比
Nacos 2.x 在性能上有显著提升:
| 指标 | Nacos 1.x | Nacos 2.x | 提升 |
|---|---|---|---|
| 服务注册 QPS | ~10,000 | ~50,000 | 5x |
| 服务发现 QPS | ~20,000 | ~100,000 | 5x |
| 配置推送延迟 | 秒级 | 毫秒级 | 10x+ |
| 连接数支持 | ~10,000 | ~100,000 | 10x |
一致性协议
Nacos 根据数据类型和一致性需求,采用不同的一致性协议:
协议选择策略
┌─────────────────────────────────────────────────────────────────┐
│ Nacos 一致性协议选择 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 数据类型判断 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────┴────────────┐ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 临时实例数据 │ │ 持久化数据 │ │
│ │ • 服务注册信息 │ │ • 持久实例 │ │
│ │ • 临时元数据 │ │ • 配置数据 │ │
│ └──────────────────┘ │ • 持久元数据 │ │
│ │ └──────────────────┘ │
│ ▼ │ │
│ ┌──────────────────┐ ▼ │
│ │ Distro 协议 │ ┌──────────────────┐ │
│ │ (AP 模型) │ │ Raft 协议 │ │
│ │ │ │ (CP 模型) │ │
│ │ • 最终一致性 │ │ • 强一致性 │ │
│ │ • 高可用优先 │ │ • 数据正确优先 │ │
│ │ • 无主架构 │ │ • 有主架构 │ │
│ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Distro 协议
Distro 协议是 Nacos 自研的分布式一致性协议,专门针对临时实例数据设计,采用 AP 模型。
设计理念
临时实例数据的特点:
- 生命周期短:实例频繁上下线
- 允许短暂不一致:最终一致性即可满足需求
- 数据量相对较小:主要是服务地址信息
Distro 协议的设计目标:
- 高可用:任何节点故障不影响服务
- 高性能:支持高并发读写
- 最终一致性:数据在短时间内达到一致
工作原理
┌─────────────────────────────────────────────────────────────────┐
│ Distro 协议工作原理 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 核心思想 │ │
│ │ 每个节点负责一部分数据,节点间相互同步各自负责的数据 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 步骤1:数据写入 │
│ ┌────────┐ 写入请求 ┌────────┐ │
│ │ Client │ ───────────────► │ Node A │ │
│ └────────┘ └────────┘ │
│ │ │
│ │ 验证数据归属 │
│ ▼ │
│ ┌──────────────┐ │
│ │ 数据属于本节点 │ │
│ │ 直接写入存储 │ │
│ └──────────────┘ │
│ │
│ 步骤2:数据同步 │
│ ┌────────┐ 同步数据 ┌────────┐ 同步数据 ┌────────┐
│ │ Node A │ ───────────────► │ Node B │ ◄───────────── │ Node C │
│ └────────┘ └────────┘ └────────┘
│ │ │ │
│ │ ◄───── 同步确认 ────── │ ──── 同步确认 ────► │
│ │
│ 步骤3:数据读取 │
│ ┌────────┐ 读取请求 ┌────────┐ │
│ │ Client │ ───────────────► │ Node B │ │
│ └────────┘ └────────┘ │
│ │ │
│ │ 判断数据归属 │
│ ▼ │
│ ┌──────────────┴──────────────┐ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 数据属于本节点 │ │ 数据属于其他节点 │ │
│ │ 本地读取返回 │ │ 转发请求获取 │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
数据分片机制
Distro 协议使用一致性哈希进行数据分片:
// 简化的数据归属判断逻辑
public boolean isResponsible(String serviceName) {
// 计算服务名的哈希值
int hash = serviceName.hashCode();
// 根据节点列表计算归属
List<String> sortedNodes = getSortedNodes();
int nodeIndex = Math.abs(hash) % sortedNodes.size();
// 判断是否属于当前节点
return sortedNodes.get(nodeIndex).equals(currentNode);
}
同步机制
Distro 采用两种同步方式:
1. 定时同步
# 同步配置
nacos.core.protocol.distro.data.sync_delay_ms=5000 # 同步间隔
nacos.core.protocol.distro.data.verify_interval_ms=5000 # 校验间隔
每个节点定期将自己负责的数据同步给其他节点。
2. 启动同步
节点启动时,主动从其他节点拉取数据,快速达到数据一致:
节点启动流程:
1. 加入集群
2. 向其他节点请求全量数据
3. 合并数据到本地
4. 开始提供服务和定时同步
故障恢复
当节点故障恢复后,Distro 协议会自动进行数据恢复:
故障恢复流程:
1. 节点重新上线
2. 检测本地数据是否过期
3. 向其他节点请求最新数据
4. 合并数据并开始服务
Raft 协议
Raft 协议用于持久化数据(配置、持久实例),采用 CP 模型,确保强一致性。
设计理念
持久化数据的特点:
- 需要强一致性:配置变更必须立即对所有节点可见
- 数据变更频率较低:配置更新不如服务注册频繁
- 数据丢失代价高:配置丢失可能导致系统故障
Raft 基本概念
Raft 协议将节点分为三种角色:
| 角色 | 说明 |
|---|---|
| Leader | 主节点,处理所有写请求,同步数据给 Follower |
| Follower | 从节点,接收 Leader 的数据同步,响应投票请求 |
| Candidate | 候选者,选举过程中的临时状态 |
选举过程
┌─────────────────────────────────────────────────────────────────┐
│ Raft 选举过程 │
│ │
│ 初始状态:所有节点都是 Follower │
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │Follower│ │Follower│ │Follower│ │
│ └────────┘ └────────┘ └────────┘ │
│ │
│ 选举超时:Follower 未收到 Leader 心跳,转为 Candidate │
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │Candidate│ │Follower│ │Follower│ │
│ └────────┘ └────────┘ └────────┘ │
│ │ │
│ │ 发送投票请求 │
│ ├───────────────────────►│ │
│ └───────────────────────►│ │
│ │
│ 投票:获得多数票后成为 Leader │
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Leader │◄─────│Follower│◄─────│Follower│ │
│ └────────┘ └────────┘ └────────┘ │
│ │ │
│ │ 发送心跳维持领导权 │
│ ├───────────────────────►│ │
│ └───────────────────────►│ │
└─────────────────────────────────────────────────────────────────┘
日志复制
Leader 接收写请求后,通过日志复制同步到所有 Follower:
┌─────────────────────────────────────────────────────────────────┐
│ Raft 日志复制 │
│ │
│ 1. 客户端发送写请求 │
│ │
│ ┌────────┐ 写入请求 ┌────────┐ │
│ │ Client │ ───────────────► │ Leader │ │
│ └────────┘ └────────┘ │
│ │ │
│ 2. Leader 追加日志,发送给 Follower │
│ │ │
│ ┌──────┴──────┐ │
│ ▼ ▼ │
│ ┌────────┐ ┌────────┐ │
│ │Follower│ │Follower│ │
│ └────────┘ └────────┘ │
│ │ │ │
│ 3. Follower 确认 └──────┬──────┘ │
│ │ │
│ 4. 多数确认后,Leader 提交日志 │
│ │ │
│ 5. Leader 响应客户端 │
│ ┌────────┐ 写入成功 ┌────────┐ │
│ │ Client │ ◄─────────────── │ Leader │ │
│ └────────┘ └────────┘ │
└─────────────────────────────────────────────────────────────────┘
Nacos 中的 Raft 实现
Nacos 1.4+ 使用 SOFA-JRaft 作为 Raft 实现:
# Raft 相关配置
nacos.core.protocol.raft.data.enabled=true
nacos.core.protocol.raft.data.election_timeout_ms=5000
nacos.core.protocol.raft.data.snapshot_interval_secs=30
数据模型
配置数据模型
Nacos 配置由三元组唯一确定:
Namespace(命名空间)+ Group(分组)+ Data ID(配置集ID)
┌─────────────────────────────────────────────────────────────────┐
│ 配置数据模型 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Namespace(命名空间) │ │
│ │ ID: dev │ │
│ │ 名称: 开发环境 │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ Group(分组) │ │ │
│ │ │ 名称: DATABASE_GROUP │ │ │
│ │ │ ┌─────────────────────────────────────────────┐ │ │ │
│ │ │ │ Data ID │ │ │ │
│ │ │ │ 名称: mysql.yaml │ │ │ │
│ │ │ │ ┌─────────────────────────────────────┐ │ │ │ │
│ │ │ │ │ 配置内容 │ │ │ │ │
│ │ │ │ │ spring: │ │ │ │ │
│ │ │ │ │ datasource: │ │ │ │ │
│ │ │ │ │ url: jdbc:mysql://... │ │ │ │ │
│ │ │ │ │ username: root │ │ │ │ │
│ │ │ │ └─────────────────────────────────────┘ │ │ │ │
│ │ │ └─────────────────────────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
服务数据模型
服务数据采用层次化结构:
┌─────────────────────────────────────────────────────────────────┐
│ 服务数据模型 │
│ │
│ Namespace(命名空间) │
│ │ │
│ ├── Group(分组) │
│ │ │ │
│ │ ├── Service(服务) │
│ │ │ │ │
│ │ │ ├── Cluster(集群) │
│ │ │ │ │ │
│ │ │ │ └── Instance(实例) │
│ │ │ │ ├── IP:Port │
│ │ │ │ ├── Weight │
│ │ │ │ ├── Metadata │
│ │ │ │ └── Health Status │
│ │ │ │ │
│ │ │ └── Cluster 2... │
│ │ │ │
│ │ └── Service 2... │
│ │ │
│ └── Group 2... │
└─────────────────────────────────────────────────────────────────┘
实体关系模型
┌─────────────────────────────────────────────────────────────────┐
│ 配置实体关系 │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Config │ 1 * │ ConfigHistory │ │
│ │ │─────────│ │ │
│ │ - dataId │ │ - id │ │
│ │ - group │ │ - dataId │ │
│ │ - tenantId │ │ - group │ │
│ │ - content │ │ - content │ │
│ │ - md5 │ │ - opType │ │
│ └──────────────┘ │ - createTime │ │
│ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Config │ * * │ Tag │ │
│ │ │─────────│ │ │
│ └──────────────┘ │ - tagName │ │
│ │ - tagType │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 服务实体关系 │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Service │ 1 * │ Instance │ │
│ │ │─────────│ │ │
│ │ - name │ │ - instanceId │ │
│ │ - group │ │ - ip │ │
│ │ - namespace │ │ - port │ │
│ │ - protect │ │ - weight │ │
│ │ Threshold │ │ - healthy │ │
│ └──────────────┘ │ - metadata │ │
│ │ - ephemeral │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
客户端架构
SDK 类视图
┌─────────────────────────────────────────────────────────────────┐
│ Nacos SDK 核心类结构 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ NamingService │ │
│ │ - registerInstance() │ │
│ │ - deregisterInstance() │ │
│ │ - selectInstances() │ │
│ │ - subscribe() / unsubscribe() │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ NamingClientProxy │ │
│ │ 统一的服务操作代理 │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ gRPC Proxy │ │ HTTP Proxy │ │ │
│ │ │ (2.x 优先) │ │ (1.x 兼容) │ │ │
│ │ └─────────────────┘ └─────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ServerListManager │ │
│ │ 管理服务端地址列表 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ConfigService │ │
│ │ - getConfig() │ │
│ │ - publishConfig() │ │
│ │ - addListener() / removeListener() │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ConfigClientProxy │ │
│ │ 配置操作代理 │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
连接管理
Nacos 2.x 客户端使用 gRPC 连接管理器维护与服务端的连接:
// 简化的连接管理逻辑
public class ConnectionManager {
private GrpcConnection connection;
private ScheduledExecutorService executor;
public void connect(String serverAddress) {
// 建立 gRPC 连接
connection = new GrpcConnection(serverAddress);
connection.connect();
// 启动心跳任务
executor.scheduleAtFixedRate(() -> {
connection.sendHeartbeat();
}, 0, 5, TimeUnit.SECONDS);
// 注册连接监听器
connection.addListener(new ConnectionListener() {
@Override
public void onConnected() {
// 连接成功,重新订阅
resubscribe();
}
@Override
public void onDisconnected() {
// 连接断开,尝试重连
reconnect();
}
});
}
}
部署模式
单机模式
适用于开发和测试环境:
# 单机模式配置
nacos.standalone=true
单机模式使用内置数据库 Derby 存储数据,无需外部依赖。
集群模式
适用于生产环境:
# 集群模式配置
nacos.standalone=false
# cluster.conf
192.168.1.101:8848
192.168.1.102:8848
192.168.1.103:8848
集群模式需要外部数据库(MySQL)存储数据,确保数据持久化。
混合部署
服务发现和配置管理可以独立部署:
┌─────────────────────────────────────────────────────────────────┐
│ 混合部署模式 │
│ │
│ 方式一:合并部署 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Nacos Server │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Naming Module │ │ Config Module │ │ │
│ │ └─────────────────┘ └─────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 方式二:分离部署 │
│ ┌───────────────────┐ ┌───────────────────┐ │
│ │ Naming Server │ │ Config Server │ │
│ │ (服务发现集群) │ │ (配置管理集群) │ │
│ └───────────────────┘ └───────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
设计原则
高可用设计
Nacos 的高可用设计体现在多个层面:
1. 无单点故障
- Distro 协议无主架构,任何节点都可处理请求
- Raft 协议自动选举,Leader 故障自动切换
2. 数据冗余
- 所有数据在多个节点存储副本
- 持久化数据存储在外部数据库
3. 故障自动恢复
- 节点故障自动剔除
- 节点恢复自动加入集群
- 数据自动同步恢复
性能优化
1. 本地缓存
客户端缓存服务列表和配置,减少网络请求:
// 客户端缓存逻辑
public class ServiceInfoHolder {
private final Map<String, ServiceInfo> serviceInfoMap;
public ServiceInfo getServiceInfo(String serviceName) {
// 先从本地缓存获取
ServiceInfo info = serviceInfoMap.get(serviceName);
if (info != null && !info.expired()) {
return info;
}
// 缓存不存在或过期,从服务端获取
return fetchFromServer(serviceName);
}
}
2. 批量操作
支持批量注册、批量获取,减少网络开销。
3. 异步处理
使用异步事件机制处理数据变更通知,避免阻塞主线程。
最佳实践
1. 合理选择实例类型
- 临时实例:适用于微服务,实例频繁上下线
- 持久实例:适用于数据库、中间件等基础设施
2. 命名空间规划
按环境和租户规划命名空间:
命名空间规划示例:
- dev:开发环境
- test:测试环境
- staging:预发布环境
- prod:生产环境
- tenant-a:租户 A
- tenant-b:租户 B
3. 分组规划
按业务模块或团队规划分组:
分组规划示例:
- DEFAULT_GROUP:默认分组
- USER_GROUP:用户服务组
- ORDER_GROUP:订单服务组
- PAYMENT_GROUP:支付服务组
4. 集群规模选择
| 规模 | 节点数 | 适用场景 |
|---|---|---|
| 小型 | 3 | 服务数 < 100,QPS < 1000 |
| 中型 | 5 | 服务数 100-500,QPS 1000-10000 |
| 大型 | 7+ | 服务数 > 500,QPS > 10000 |