5000 字 | 14 图 | 揭秘 Nacos 的 AP 架构 「Distro 一致性协议」

2022-05-13 17:03:17 浏览数 (1)

这是悟空的第 146 篇原创文章

官网:www.passjava.cn

你好,我是悟空呀。

前言

上篇我们讲解了 Nacos 的架构原理:一条注册请求会经历什么。

Nacos 架构原理①:一条注册请求会经历什么?

这次我们要进入 Nacos 的一致性底层原理了,还是先来一张架构图,让大家对 Nacos 的架构有个整体的印象,本篇会主要讲解一致性模块中的 Distro 协议。

上篇留了两个知识点:

  • ① 服务实例注册到 Nacos 节点后,通过 UDP 方式推送到所有服务实例。让其他服务实例感知到服务列表的变化。
  • ② 如何复制数据到其他节点:当前 Nacos 节点开启 1s 的延迟任务,将数据同步给其他 Nacos 节点。(分区一致性)

第 ② 个知识点就是 Nacos 自研的 Distro 一致性协议的核心功能。

首先这个 Distro 协议是针对集群环境的,比如下面这三个集群节点组成了一个集群。服务 A 和服务 B 会往这个集群进行注册。

Nacos 集群节点

Nacos 集群环境

我们知道 Nacos 它是支持两种分布式定理的:CP(分区一致性)和 AP(分区可用性) ,而 AP 是通过 Nacos 自研的 Distro 协议来保证的,CP 是通过 Nacos 的 JRaft 协议来保证的。

因为注册中心作为系统中很重要的的一个服务,需要尽最大可能对外提供可用的服务,所以选择 AP 来保证服务的高可用,另外 Nacos 还采取了心跳机制来自动完成服务数据补偿的机制,所以说 Distro 协议是弱一致性的。

如果采用 CP 协议,则需要当前集群可用的节点数过半才能工作。

关于 CP 和 AP 的理论知识,可以参考这篇:用太极拳讲分布式理论 CAP 和 BASE,真舒服!

问题:Nacos 哪些地方用到了 AP 和 CP?

  • 针对临时服务实例,采用 AP 来保证注册中心的可用性,Distro 协议。
  • 针对持久化服务实例,采用 CP 来保证各个节点的强一致性,JRaft 协议。(JRaft 是 Nacos 对 Raft 的一种改造)
  • 针对配置中心,无 Database 作为存储的情况下,Nacos 节点之间的内存数据为了保持一致,采用 CP。Nacos 提供这种模式只是为了方便用户本机运行,降低对存储依赖,生产环境一般都是通过外置存储组件来保证数据一致性。
  • 针对配置中心,有 Database 作为存储的情况下,Nacos 通过持久化后通知其他节点到数据库拉取数据来保证数据一致性,另外采用读写分离架构来保证高可用,所以这里我认为这里采用的 AP,欢迎探讨。
  • 针对 异地多活,采用 AP 来保证高可用。

弦外音:

临时服务实例就是我们默认使用的 Nacos 注册中心模式,客户端注册后,客户端需要定时上报心跳信息来进行服务实例续约。这个在注册的时候,可以通过传参设置是否是临时实例。

持久化服务实例就是不需要上报心跳信息的,不会被自动摘除,除非手动移除实例,如果实例宕机了,Nacos 只会将这个客户端标记为不健康。

本篇会带着大家从源码角度来深入剖析下 Distro 协议。

知识点预告:

  • ① Distro 的设计思想和六大机制。
  • ② Nacos 如何同步数据到其他节点。(异步复制机制,本篇重点讲解)
  • ③ Nacos 如何保证所有节点的数据一致性。(定期检验;健康检查机制,下一篇重点讲解)
  • ④ 新加入的 Nacos 节点,如何进行拉取数据。(新节点同步机制)

一、Distro 的设计思想和六大机制

Distro 协议是 Nacos 对于临时实例数据开发的一致性协议。

Distro 协议是集 Gossip Eureka 协议的优点并加以优化后出现的。

关于 Gossip 协议,可以看这篇:病毒入侵:全靠分布式 Gossip 协议

Gossip 协议有什么坑?由于随机选取发送的节点,不可避免地存在消息重复发送给同一节点的情况,增加了网络的传输的压力,给消息节点带来额外的处理负载。

Distro 协议的优化:每个节点负责一部分数据,然后将数据同步给其他节点,有效地降低了消息冗余的问题。

关于临时实例数据:临时数据其实是存储在内存缓存中的,并且在其他节点在启动时会进行全量数据同步,然后节点也会定期进行数据校验。

大家不要被这个协议吓到,其实就是阿里自己实现的一套同步逻辑。

AP 中的 P 代表网络分区,所以 Distro 在分布式集群环境下才能真正发挥其作用。它保证了在多个 Nacos 节点组成的 Nacos 集群环境中,当其中某个 Nacos 宕机后,整个集群还是能正常工作。

Distro 的设计机制:

  • 平等机制:Nacos 的每个节点是平等的,都可以处理写的请求。(上一讲已经重点讲解了✅)
  • 异步复制机制:Nacos 把变更的数据异步复制到其他节点。(⭐️重点讲解)
  • 健康检查机制:每个节点只存了部分数据,定期检查客户端状态保持数据一致性。
  • 本地读机制: 每个节点独立处理读请求,及时从本地发出响应。
  • 新节点同步机制:Nacos 启动时,从其他节点同步数据。
  • 路由转发机制:客户端发送的写请求,如果属于自己则处理,否则路由转发给其他节点。(上一讲已经重点讲解了✅)

Distro 的设计机制

二、异步复制机制:写入数据后如何同步给其他节点

2.1 核心入口

核心源码路径:

代码语言:javascript复制
/naming/consistency/ephemeral/distro/DistroConsistencyServiceImpl.java

这个类的名字就说明它是 Distro 一致性协议的接口实现类。

当注册请求交给 Nacos 节点来处理时,核心入口方法就是 put(),如下图所示:

上一讲我们已经说过,这里面会做几件事:

添加实例信息的流程

  • ① 将实例信息存放到内存缓存 concurrentHashMap 里面。
  • ② 添加一个任务到 BlockingQueue 里面,这个任务就是将最新的实例列表通过 UDP 的方式推送给所有客户端(服务实例),这样客户端就拿到了最新的服务实例列表,缓存到本地。
  • ③ 开启 1s 的延迟任务,将数据通过给其他 Nacos 节点。

说明:第二件事是 Nacos 和 客户端如何保持数据一致性的,第三件事是 Nacos 集群间如何保持数据一致性的,因本篇重点讲解 Nacos 的 AP 原理,所以会针对第三件事来进行阐述。而第二件事,会在后续文章中重点讲解。

2.2 sync 方法的参数说明

首先我们来看下 distroProtocol.sync(),这个方法传了哪些参数:

  • 第一个参数 new DistroKey(),它里面传了 key 和一个常量。

key:就是客户端的服务名,示例值如下:

代码语言:javascript复制
com.alibaba.nacos.naming.iplist.ephemeral.public##DEFAULT_GROUP@@nacos.naming.serviceName

INSTANCE_LIST_KEY_PREFIX:就是 com.alibaba.nacos.naming.iplist.

然后这两个参数组装成一个 DistroKey。

  • 第二个参数是同步数据的类型,这里为 change。
  • 第三个参数是同步任务的延迟时间,1s。

2.3 sync 的核心逻辑:添加任务

先上一张原理图帮助大家理解,流程图如下所示。核心逻辑分为以下几步。

  • 遍历其他节点,拿到节点信息。
  • 判断这个任务在 map 中是否存在,如果存在则合并这个 task。
  • 如果不存在,则加到 map 中。
  • 后台线程遍历这个 map,拿到任务。

添加任务到 map 中

代码的时序图如下所示:

sync 的核心代码时序图

  • 第一个类 DistroConsistencyServiceImpl 把实例信息加入 map 中,后续通过 UDP方式推送给客户端。
  • 第二个类 DistroProtocol 主要就是循环遍历其他节点。
  • 第三个类 NacosDelayTaskExecuteEngine 是核心类,创建了一个同步的任务到 ConcurrentHashMap 中。

2.4 sync 的核心逻辑:后台线程异步复制数据

先说下哈,这个核心逻辑极其复杂,我们看的时候需要抓主线,知道其中几个关键点就可以了。

悟空在画代码逻辑图的时候,内心是崩溃的,Nacos 为什么写这么复杂啊!大家不用细看,看了也会懵

0 人点赞