这篇文章要分享的是 Nacos 集群的 Distro一致性协议。
如下图所示,Nacos Client 向 Nacos Server 集群中的一个服务节点注册信息,这些服务端节点之间再进行数据同步,应用的基础原理就是 Distro 一致性协议。

Distro一致性协议
Distro 协议是 Nacos 对于临时实例数据开发的一致性协议,是阿里巴巴的私有协议。
Distro 的设计机制
新节点同步机制:Nacos 启动时,从其他节点同步数据。
平等机制:Nacos 的每个节点是平等的,都可以处理写的请求。
路由转发机制:客户端发送的写请求,如果属于自己则处理,否则路由转发给其他节点。
异步复制机制:Nacos 把变更的数据异步复制到其他节点。
健康检查机制:每个节点只存了部分数据,定期检查客户端状态保持数据一致性。
本地读机制: 每个节点独立处理读请求,及时从本地发出响应。
Nacos 源码解析
接下来就从 Nacos 源码的角度来学习 Distro 的原理。
新节点同步机制
com.alibaba.nacos.core.distributed.distro.DistroProtocol#startLoadTask
如果此时新加入一个 Nacos Server 节点,会同步从其他节点拉取数据。
java
GlobalExecutor.submitLoadDataTask(
new DistroLoadDataTask(memberManager, distroComponentHolder, DistroConfig.getInstance(), loadCallback));
看到线程池,便知道这是一个异步任务。
java
load();
if (!checkCompleted()) {
GlobalExecutor.submitLoadDataTask(this, distroConfig.getLoadDataRetryDelayMillis());
} else {
loadCallback.onSuccess();
Loggers.DISTRO.info("[DISTRO-INIT] load snapshot data success");
}
一上来先拉取其他节点的数据,只要有一个成功了就算通过。之后再检查是否全量同步成功,没有的话,30秒之后再重新同步一次。
平等机制
com.alibaba.nacos.naming.web.DistroFilter
每个节点处理部分写请求,通过算法计算属于自己的请求就处理,不属于自己的请求转发到正确的节点。
java
public boolean responsible(String responsibleTag) {
final List<String> servers = healthyList;
if (!switchDomain.isDistroEnabled() || EnvUtil.getStandaloneMode()) {
return true;
}
if (CollectionUtils.isEmpty(servers)) {
// means distro config is not ready yet
return false;
}
String localAddress = EnvUtil.getLocalAddress();
int index = servers.indexOf(localAddress);
int lastIndex = servers.lastIndexOf(localAddress);
if (lastIndex < 0 || index < 0) {
return true;
}
int target = distroHash(responsibleTag) % servers.size();
return target >= index && target <= lastIndex;
}
路由转发机制
com.alibaba.nacos.naming.web.DistroFilter
路由转发机制和平等机制为上下文关系,不是自己处理的请求,就计算出真正要处理的服务地址,然后转发给它处理。
java
final String targetServer = distroMapper.mapSrv(distroTag);
异步复制机制
com.alibaba.nacos.core.distributed.distro.DistroProtocol#sync
如果自己所在的服务端数据发生了变更,就把数据异步复制给集群中的其他节点。
java
public void sync(DistroKey distroKey, DataOperation action, long delay) {
for (Member each : memberManager.allMembersWithoutSelf()) {
syncToTarget(distroKey, action, each.getAddress(), delay);
}
}
健康检查机制
服务端集群每个节点处理数据有快有慢,这些数据到底是否全部一致呢?这就需要健康检查机制。线程池提交异步任务,每隔5秒检查一次。
每个服务端节点只负责检查自己负责的客户端的数据是否一致,不是自己对应的客户端不处理。
java
private void startVerifyTask() {
GlobalExecutor.schedulePartitionDataTimedSync(new DistroVerifyTimedTask(memberManager, distroComponentHolder,
distroTaskEngineHolder.getExecuteWorkersManager()),
DistroConfig.getInstance().getVerifyIntervalMillis());
}
本地读机制
com.alibaba.nacos.naming.controllers.InstanceController#list
每个服务端节点最终会存储全部的客户端数据,只要 Nacos Client 请求,会从本地直接返回数据,而不是从其他服务器拉取数据,相当于提升了服务端的响应速度。
数据全部存储在com.alibaba.nacos.naming.core.Service#clusterMap 内存集合中,所以速度非常快。
总结:Distro 协议最关键的部分是每个节点只负责一部分写数据。