
上篇文章我们剖析了Nacos架构中一条注册请求的完整流程。本篇将深入其一致性模块的底层,重点解析保证Nacos高可用性的关键——自研的 Distro一致性协议。为了让大家对整体架构有清晰的认识,我们先回顾一下Nacos的核心架构图。

上篇结尾留了两个伏笔:
- 服务实例注册到Nacos节点后,会通过UDP推送给所有客户端,使其感知服务列表变化。
- 当前节点如何将数据复制到其他Nacos节点,以保障集群数据的一致性。
第二点,正是Distro协议的核心功能。Distro协议作用于集群环境,例如下图中由三个节点组成的Nacos集群,服务A和服务B会向这个集群进行注册。


Nacos支持CAP理论中的两种模式:CP(分区一致性)和AP(分区可用性)。AP模式正是通过自研的Distro协议来保障的,而CP模式则由JRaft协议(基于Raft改造)负责。
为什么注册中心倾向于选择AP? 因为注册中心作为系统的关键基础设施,首要目标是最大程度保证可用性。Distro协议通过牺牲强一致性来换取高可用,是一种最终一致性协议。此外,Nacos还结合了心跳机制进行服务数据的自动补偿,进一步确保了服务的健壮性。相比之下,如果采用CP协议,则要求集群中过半节点可用才能正常工作。
那么,Nacos在哪些场景下分别使用AP和CP呢?
- 临时服务实例:采用AP模式,使用Distro协议,以保证注册中心的可用性。这是我们最常用的模式。
- 持久化服务实例:采用CP模式,使用JRaft协议,保证各节点间的强一致性。
- 配置中心(无外置存储):节点间内存数据保持一致,采用CP。此模式主要用于简化本地部署,生产环境通常依赖外置存储。
- 配置中心(有外置存储):数据持久化到数据库后通知其他节点拉取,并采用读写分离架构,这里可以认为是AP模式。
- 异地多活场景:采用AP以保证高可用。
小提示:临时实例即默认模式,客户端需定时上报心跳续约;持久化实例则无需心跳,不会被自动剔除,仅标记健康状态。
接下来,我们将从源码层面深入剖析Distro协议,本次内容将围绕以下几个核心知识点展开:
- Distro协议的设计思想与六大核心机制。
- Nacos如何异步复制数据到其他节点(本篇重点)。
- Nacos如何通过健康检查机制保证节点间数据一致性(下篇重点)。
- 新加入的Nacos节点如何完成数据同步。
一、Distro的设计思想与六大机制
Distro协议是Nacos专门为临时实例数据设计的一致性协议。它融合并优化了Gossip和Eureka协议的优点。
Gossip协议通过随机选取节点传播消息,虽然简单健壮,但不可避免地存在消息重复发送的问题,增加了网络和处理负载。Distro协议对此进行了优化:每个节点负责一部分数据,并负责将这部分数据的变更同步给其他节点,从而显著降低了消息冗余。
临时数据主要存储在内存中,节点启动时会进行全量同步,并定期校验数据。本质上,Distro是阿里为实现特定同步逻辑而定制的一套方案。其设计目标是在由多个Nacos节点组成的集群中,即使某个节点宕机,整个集群依然能正常对外提供服务,这正是AP中“P”(分区容忍)的体现。
Distro协议主要依靠六大机制来运作:
- 平等机制:每个Nacos节点地位平等,都可以处理写请求。
- 异步复制机制:节点将数据的变更异步复制到其他节点。(⭐️本篇核心)
- 健康检查机制:每个节点只存储部分数据,通过定期检查客户端状态来维持数据一致性。
- 本地读机制:每个节点都能独立处理读请求,直接从本地响应。
- 新节点同步机制:新节点启动时,会从其他节点同步全量数据。
- 路由转发机制:节点收到写请求时,若该数据不属于自己负责,则将其转发给负责的节点处理。

二、异步复制机制:写入数据后如何同步给其他节点
2.1 核心入口
当注册请求由某个Nacos节点处理时,其核心入口是DistroConsistencyServiceImpl类的put()方法。
@Override
public void put(String key, Record value) throws NacosException {
// 将实例信息存放到 concurrentHashMap 里面
onPut(key, value);
// 开启 1s 的延迟任务
distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE, DistroConfig.getInstance().getSyncDelayMillis());
}
回顾一下,该方法主要完成三件事:
- 将实例信息存入内存的ConcurrentHashMap。
- 向任务队列添加一个任务,用于通过UDP将最新实例列表推送给所有客户端。
- 开启一个1秒的延迟任务,将数据同步给其他Nacos节点。

第二点是保证Nacos服务器与客户端一致性的关键,第三点是保证Nacos集群内部一致性的关键。本篇我们聚焦第三点,即集群间的数据同步。
2.2 sync方法参数解析
distroProtocol.sync()方法接收三个参数:
new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX)
key:服务名称,格式如:com.alibaba.nacos.naming.iplist.ephemeral.public##DEFAULT_GROUP@@nacos.naming.serviceName
INSTANCE_LIST_KEY_PREFIX:常量字符串 com.alibaba.nacos.naming.iplist.
- 数据操作类型,此处为
DataOperation.CHANGE。
- 同步延迟时间,默认
1s。
2.3 sync核心逻辑:任务添加
其核心逻辑是创建一个延迟同步任务。流程可以概括为:
- 遍历其他Nacos节点。
- 判断该同步任务是否已存在于任务Map中。
- 若存在,则合并任务;若不存在,则将新任务加入Map。
- 后台线程会持续遍历该Map,取出任务执行。

从代码时序来看,调用链路如下:
DistroConsistencyServiceImpl.onPut():先将实例信息存入Map,为后续UDP推送做准备。
DistroConsistencyServiceImpl.sync():调用DistroProtocol.sync()。
DistroProtocol.sync():遍历其他节点,准备同步。
NacosDelayTaskExecuteEngine.addTask():将具体的同步任务放入一个ConcurrentHashMap中管理。

2.4 sync核心逻辑:后台线程异步复制
这是整个流程中最复杂的部分,我们抓住主线即可。核心步骤如下:
- 遍历其他节点,为每个节点创建同步任务,加入任务Map。
- 后台线程从Map中取出任务。
- 将任务放入一个执行队列。
- 专门的工作线程(Worker)从队列中取出任务执行。
- 任务内容是向其他Nacos节点发送HTTP POST请求,请求体中包含序列化后的实例数据。请求URL示例:
http://192.168.0.101:8858/nacos/v1/ns/distro/datum。

2.5 其他节点如何处理同步请求
2.5.1 数据存储结构
接收方节点处理请求的逻辑相对直接:存储注册信息,并通知客户端。
注册信息被封装在Datum对象中,多个Datum再存储在DataStore中。数据结构关系如下:
Datum:包含value(客户端实例列表,如ArrayList)、key(服务名)、timestamp。
DataStore:本质是一个ConcurrentHashMap<String, Datum>。

2.5.2 源码分析
根据请求URL /nacos/v1/ns/distro/datum,找到处理类DistroController.onSyncDatum()。其主要工作:
- 将实例信息封装为Datum,存入DataStore(即内存ConcurrentHashMap)。
- 触发UDP推送,将变更通知给订阅该服务的所有客户端。
public void onPut(String key, Record value) {
if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
Datum<Instances> datum = new Datum<>();
datum.value = (Instances) value;
datum.key = key;
datum.timestamp.incrementAndGet();
// 1、存放到内存中
dataStore.put(key, datum);
}
if (!listeners.containsKey(key)) {
return;
}
// 2、推送数据到其他客户端
notifier.addTask(key, DataOperation.CHANGE);
}
三、定时同步:如何保持数据一致性
3.1 为什么需要定时同步
在集群模式下,客户端只需与一个Nacos节点通信。但理想状态下,每个Nacos节点都应包含全量数据,这样客户端从任意节点都能读到完整的注册表。那么,Nacos集群是如何利用Distro协议维持这种数据一致性的呢?
3.2 定期检验元数据(v1版本方案)
在早期v1版本中,Nacos采用定期校验元数据(数据指纹)的方式。每个节点定期向其他节点发送校验请求,比对双方数据的MD5值。

如果发现不一致,则发起全量数据拉取请求以补齐差异。
请求示例:http://其他节点IP:端口/nacos/v1/ns/distro/checksum?source=本机IP:端口
3.3 版本迭代说明
在v2版本中,上述定期校验机制已被更高效的健康检查机制所取代。该机制不仅能维护客户端与Nacos节点间的状态,也用于在集群节点间同步和修复数据。因其内容较多,我们将在下一篇文章中详细探讨Nacos的健康检查机制。
四、新节点同步机制
4.1 原理
新加入集群的Distro节点会主动从现有节点全量拉取所有非持久化实例数据。完成后,集群中每个节点都维护了完整的注册表数据。

4.2 源码分析
DistroProtocol在初始化时会启动一个加载任务(DistroLoadDataTask),其run()方法会调用loadAllDataSnapshotFromRemote(),从远程节点拉取全量数据快照。
五、本地读机制
5.1 原理
尽管每个Nacos节点只负责写入自己管辖的数据,但它们都通过异步复制机制持有了全量数据的副本。因此,对于客户端的读请求(如查询服务列表),可以直接由接收请求的节点本地响应,无需跨节点查询。

这种设计极大保障了系统的高可用性(AP):
- 快速响应:读操作无远程交互,延迟极低。
- 容忍脑裂:即使发生网络分区,每个节点仍能独立提供读服务(数据可能暂时不一致)。待网络恢复后,通过健康检查等机制最终达成一致。
六、总结
本文通过原理图结合源码主线的方式,系统阐述了Nacos为实现高可用(AP)而自研的Distro一致性协议。该协议通过平等机制、异步复制、健康检查、本地读、新节点同步、路由转发六大机制协同工作,在分布式系统的集群环境中,巧妙地在可用性与一致性之间取得了平衡,确保了作为微服务核心的注册中心能够持续稳定地提供服务。理解这些机制,对于设计和维护高可用的微服务架构至关重要。