跳到主要内容

架构原理

理解 Nacos 的架构设计有助于更好地使用和运维 Nacos。本章深入介绍 Nacos 的核心架构、一致性协议和数据模型。

整体架构

Nacos 采用分层架构设计,将服务发现和配置管理两大功能融合在一个平台上。

逻辑架构图

┌─────────────────────────────────────────────────────────────────────────┐
│ Nacos 逻辑架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ API 层 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ OpenAPI │ │ Console │ │ SDK │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 核心模块层 │ │
│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │
│ │ │ 服务管理 │ │ 配置管理 │ │ 元数据管理 │ │ │
│ │ │ • 服务注册 │ │ • 配置存储 │ │ • 标签管理 │ │ │
│ │ │ • 服务发现 │ │ • 配置推送 │ │ • 数据索引 │ │ │
│ │ │ • 健康检查 │ │ • 版本管理 │ │ │ │ │
│ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 协议与通信层 │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Distro 协议(临时实例) Raft 协议(持久实例/配置) │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ HTTP/REST API │ gRPC(2.x 新增长连接) │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 存储层 │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ 内置存储(Derby) │ 外部存储(MySQL/PostgreSQL) │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘

核心组件说明

组件功能
OpenAPI提供 RESTful 风格的 HTTP 接口,支持多语言集成
ConsoleWeb 控制台,方便进行服务和配置管理
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、控制台
98488848 + 1000客户端 gRPC 请求端口
98498848 + 1001服务端 gRPC 通信端口
78488848 - 100Raft 协议端口
端口转发注意

使用 VIP/Nginx 时,需要配置 TCP 转发而非 HTTP 代理,否则会导致 gRPC 连接中断。gRPC 基于 HTTP/2,Nginx 需要支持 HTTP/2 代理。

性能对比

Nacos 2.x 在性能上有显著提升:

指标Nacos 1.xNacos 2.x提升
服务注册 QPS~10,000~50,0005x
服务发现 QPS~20,000~100,0005x
配置推送延迟秒级毫秒级10x+
连接数支持~10,000~100,00010x

一致性协议

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

下一步