Nacos 架构
Nacos 开源之前在阿里内部已经发展了十年,沉淀了很多优秀的能力,也有很多历史负担,在开源
的时候我们取其精华进行开源,为了提升代码的健壮性和扩展性,进行了充分的分层和模块化设计。
设计原则
极简原则,简单才好用,简单才稳定,简单才易协作。
架构⼀致性,⼀套架构要能适应开源、内部、商业化(公有云及专有云)3 个场景。
扩展性,以开源为内核,商业化做基础,充分扩展,方便用户扩展。
模块化,将通用部分抽象下沉,提升代码复用和健壮性。
长期主义,不是要⼀个能支撑未来 3 年的架构,而是要能够支撑 10 年的架构。
开放性,设计和讨论保持社区互动和透明,方便大家协作。
架构图
整体架构分为用户层、业务层、内核层和插件,用户层主要解决用户使用的易用性问题,业务层主
要解决服务发现和配置管理的功能问题,内核层解决分布式系统⼀致性、存储、高可用等核心问题,
插件解决扩展性问题
![](https://i-blog.csdnimg.cn/blog_migrate/d995d6ebe581c6e096266952662941c5.png)
用户层
OpenAPI:暴露标准 Rest 风格 HTTP 接口,简单易用,方便多语言集成。
Console:易用控制台,做服务管理、配置管理等操作。
SDK:多语言 SDK,目前几乎支持所有主流编程语言。
Agent:Sidecar 模式运行,通过标准 DNS 协议与业务解耦。
CLI:命令行对产品进行轻量化管理,像 git ⼀样好用。
业务层
服务管理:实现服务 CRUD,域名 CRUD,服务健康状态检查,服务权重管理等功能。
配置管理:实现配置管 CRUD,版本管理,灰度管理,监听管理,推送轨迹,聚合数据等功能。
元数据管理:提供元数据 CURD 和打标能力,为实现上层流量和服务灰度非常关键。
19 > Nacos 架构
内核层
插件机制:实现三个模块可分可合能力,实现扩展点 SPI 机制,用于扩展自己公司定制。
事件机制:实现异步化事件通知,SDK 数据变化异步通知等逻辑,是 Nacos 高性能的关键部分。
日志模块:管理日志分类,日志级别,日志可移植性(尤其避免冲突),日志格式,异常码+帮
助文档。
回调机制:SDK 通知数据,通过统⼀的模式回调用户处理。接口和数据结构需要具备可扩展性。
寻址模式:解决 Server IP 直连,域名访问,Nameserver 寻址、广播等多种寻址模式,需要可
扩展。
推送通道:解决 Server 与存储、Server 间、Server 与 SDK 间高效通信问题。
容量管理:管理每个租户,分组下的容量,防止存储被写爆,影响服务可用性。
流量管理:按照租户,分组等多个维度对请求频率,长链接个数,报文大小,请求流控进行控制。
缓存机制:容灾目录,本地缓存,Server 缓存机制,是 Nacos 高可用的关键。
启动模式:按照单机模式,配置模式,服务模式,DNS 模式模式,启动不同的模块。
⼀致性协议:解决不同数据,不同⼀致性要求情况下,不同⼀致性要求,是 Nacos 做到 AP 协
议的关键。
存储模块:解决数据持久化、非持久化存储,解决数据分片问题。
插件
Nameserver:解决 Namespace 到 ClusterID 的路由问题,解决用户环境与 Nacos 物理环境
映射问题。
CMDB:解决元数据存储,与三方 CMDB 系统对接问题,解决应用,人,资源关系。
Metrics:暴露标准 Metrics 数据,方便与三方监控系统打通。
Trace:暴露标准 Trace,方便与 SLA 系统打通,日志白平化,推送轨迹等能力,并且可以和计
量计费系统打通。
接入管理:相当于阿里云开通服务,分配身份、容量、权限过程。
用户管理:解决用户管理,登录,SSO 等问题。
权限管理:解决身份识别,访问控制,角色管理等问题。
Nacos 架构 < 20
审计系统:扩展接口方便与不同公司审计系统打通。
通知系统:核心数据变更,或者操作,方便通过 SMS 系统打通,通知到对应人数据变更。
在单体架构的时候我们可以将配置写在配置文件中,但有⼀个缺点就是每次修改配置都需要重启服
务才能生效。
当应用程序实例比较少的时候还可以维护。如果转向微服务架构有成百上千个实例,每修改⼀次配
置要将全部实例重启,不仅增加了系统的不稳定性,也提高了维护的成本。
那么如何能够做到服务不重启就可以修改配置?所有就产生了四个基础诉求:
需要支持动态修改配置
需要动态变更有多实时
变更快了之后如何管控控制变更风险,如灰度、回滚等
敏感配置如何做安全配置
2. SDK 可以提供发布配置、更新配置、监听配置等功能。
3. SDK 通过 GRPC 长连接监听配置变更,Server 端对比 Client 端配置的 MD5 和本地 MD5
是否相等,不相等推送配置变更。
4. SDK 会保存配置的快照,当服务端出现问题的时候从本地获取。
配置资源模型
Namespace 的设计就是用来进行资源隔离的,我们在进行配置资源的时候可以从以下两个角度来
看:
从单个租户的角度来看,我们要配置多套环境的配置,可以根据不同的环境来创建 Namespace 。
比如开发环境、测试环境、线上环境,我们就创建对应的 Namespace(dev、test、prod),
Nacos 会自动生成对应的 Namespace Id 。如果同⼀个环境内想配置相同的配置,可以通过
Group 来区分。如下图所示:
Nacos 存储配置有几个比较重要的表分别是:
config_info 存储配置信息的主表,里面包含 dataId、groupId、content、tenantId、encrypt
edDataKey 等数据。
config_info_beta 灰度测试的配置信息表,存储的内容和 config_info 基本相似。有⼀个 beta
_ips 字段用于客户端请求配置时判断是否是灰度的 ip。
config_tags_relation 配置的标签表,在发布配置的时候如果指定了标签,那么会把标签和配置
的关联信息存储在该表中。
his_config_info 配置的历史信息表,在配置的发布、更新、删除等操作都会记录⼀条数据,可
以做多版本管理和快速回滚。
Nacos 内核设计
Nacos ⼀致性协议
为什么 Nacos 需要⼀致性协议
Nacos 在开源支持就定下了⼀个目标,尽可能的减少用户部署以及运维成本,做到用户只需要⼀个
程序包,就可以快速以单机模式启动 Nacos 或者以集群模式启动 Nacos。而 Nacos 是⼀个需要
存储数据的⼀个组件,因此,为了实现这个目标,就需要在 Nacos 内部实现数据存储。单机下其
实问题不大,简单的内嵌关系型数据库即可;但是集群模式下,就需要考虑如何保障各个节点之间
的数据⼀致性以及数据同步,而要解决这个问题,就不得不引入共识算法,通过算法来保障各个节
点之间的数据的⼀致性。
为什么 Nacos 选择了 Raft 以及 Distro
为什么 Nacos 会在单个集群中同时运行 CP 协议以及 AP 协议呢?这其实要从 Nacos 的场景出
发的:Nacos 是⼀个集服务注册发现以及配置管理于⼀体的组件,因此对于集群下,各个节点之间
的数据⼀致性保障问题,需要拆分成两个方面
从服务注册发现来看
服务发现注册中心,在当前微服务体系下,是十分重要的组件,服务之间感知对方服务的当前可正
常提供服务的实例信息,必须从服务发现注册中心进行获取,因此对于服务注册发现中心组件的可
用性,提出了很高的要求,需要在任何场景下,尽最大可能保证服务注册发现能力可以对外提供服
务;同时 Nacos 的服务注册发现设计,采取了心跳可自动完成服务数据补偿的机制。如果数据丢
失的话,是可以通过该机制快速弥补数据丢失。
29 > Nacos 架构
因此,为了满足服务发现注册中心的可用性,强⼀致性的共识算法这里就不太合适了,因为强⼀致
性共识算法能否对外提供服务是有要求的,如果当前集群可用的节点数没有过半的话,整个算法直
接“罢工”,而最终⼀致共识算法的话,更多保障服务的可用性,并且能够保证在⼀定的时间内各
个节点之间的数据能够达成⼀致。
上述的都是针对于 Nacos 服务发现注册中的非持久化服务而言(即需要客户端上报心跳进行服务实
例续约)。而对于 Nacos 服务发现注册中的持久化服务,因为所有的数据都是直接使用调用 Nacos
服务端直接创建,因此需要由 Nacos 保障数据在各个节点之间的强⼀致性,故而针对此类型的服务
数据,选择了强⼀致性共识算法来保障数据的⼀致性。
从配置管理来看
配置数据,是直接在 Nacos 服务端进行创建并进行管理的,必须保证大部分的节点都保存了此配
置数据才能认为配置被成功保存了,否则就会丢失配置的变更,如果出现这种情况,问题是很严重
的,如果是发布重要配置变更出现了丢失变更动作的情况,那多半就要引起严重的现网故障了,因
此对于配置数据的管理,是必须要求集群中大部分的节点是强⼀致的,而这里的话只能使用强⼀致
性共识算法。
为什么是 Raft 和 Distro 呢
对于强⼀致性共识算法,当前工业生产中,最多使用的就是 Raft 协议,Raft 协议更容易让人理解,
并且有很多成熟的工业算法实现,比如蚂蚁金服的 JRaft、Zookeeper 的 ZAB、Consul 的 Raft、
百度的 braft、Apache Ratis;因为 Nacos 是 Java 技术栈,因此只能在 JRaft、ZAB、Apache
Ratis 中选择,但是 ZAB 因为和 Zookeeper 强绑定,再加上希望可以和 Raft 算法库的支持团队
随时沟通交流,因此选择了 JRaft,选择 JRaft 也是因为 JRaft 支持多 RaftGroup,为 Nacos 后
面的多数据分片带来了可能。
Nacos 架构 < 30
而 Distro 协议是阿里巴巴自研的⼀个最终⼀致性协议,而最终⼀致性协议有很多,比如 Gossip、
Eureka 内的数据同步算法。而 Distro 算法是集 Gossip 以及 Eureka 协议的优点并加以优化而出
来的,对于原生的 Gossip,由于随机选取发送消息的节点,也就不可避免的存在消息重复发送给同
⼀节点的情况,增加了网络的传输的压力,也给消息节点带来额外的处理负载,而 Distro 算法引入
了权威 Server 的概念,每个节点负责⼀部分数据以及将自己的数据同步给其他节点,有效的降低
了消息冗余的问题。
早期的 Nacos ⼀致性协议
我们先来看看早起的 Naocs 版本的架构
![](https://i-blog.csdnimg.cn/blog_migrate/1f8b9470c1356cc520ae282070aa625d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2d9f7ad95ca3c7083fdd738d67984d39.png)
Nacos 自研 Distro 协议
背景
Distro 协议是 Nacos 社区自研的⼀种 AP 分布式协议,是面向临时实例设计的⼀种分布式协议,
其保证了在某些 Nacos 节点宕机后,整个临时实例处理系统依旧可以正常工作。作为⼀种有状态
的中间件应用的内嵌协议,Distro 保证了各个 Nacos 节点对于海量注册请求的统⼀协调和存储。
设计思想
Distro 协议的主要设计思想如下:
Nacos 每个节点是平等的都可以处理写请求,同时把新数据同步到其他节点。
每个节点只负责部分数据,定时发送自己负责数据的校验值到其他节点来保持数据⼀致性。
每个节点独立处理读请求,及时从本地发出响应。
下面几节将分为几个场景进行 Distro 协议工作原理的介绍。
数据初始化
新加入的 Distro 节点会进行全量数据拉取。具体操作是轮询所有的 Distro 节点,通过向其他的机
器发送请求拉取全量数据。
![](https://i-blog.csdnimg.cn/blog_migrate/c1766d095dc93feed6730e2467859389.png)
在全量拉取操作完成之后,Nacos 的每台机器上都维护了当前的所有注册上来的非持久化实例数
据。
数据校验
在 Distro 集群启动之后,各台机器之间会定期的发送心跳。心跳信息主要为各个机器上的所有数据的元信息(之所以使用元信息,是因为需要保证网络中数据传输的量级维持在⼀个较低水平)。这种数据校验会以心跳的形式进行,即每台机器在固定时间间隔会向其他机器发起⼀次数据校验请求。
![](https://i-blog.csdnimg.cn/blog_migrate/18c58bcbc8effaae5a37fed2342fcfea.png)
⼀旦在数据校验过程中,某台机器发现其他机器上的数据与本地数据不⼀致,则会发起⼀次全量拉
取请求,将数据补齐。
前置的 Filter 拦截请求,并根据请求中包含的 IP 和 port 信息计算其所属的 Distro 责任节点,
并将该请求转发到所属的 Distro 责任节点上。
责任节点上的 Controller 将写请求进行解析。
Distro 协议定期执行 Sync 任务,将本机所负责的所有的实例信息同步到其他节点上。
读操作
由于每台机器上都存放了全量数据,因此在每⼀次读操作中,Distro 机器会直接从本地拉取数据。
快速响应。
这种机制保证了 Distro 协议可以作为⼀种 AP 协议,对于读操作都进行及时的响应。在网络分区
的情况下,对于所有的读操作也能够正常返回;当网络恢复时,各个 Distro 节点会把各数据分片的
数据进行合并恢复。
小结
Distro 协议是 Nacos 对于临时实例数据开发的⼀致性协议。其数据存储在缓存中,并且会在启动
时进行全量数据同步,并定期进行数据校验。
在 Distro 协议的设计思想下,每个 Distro 节点都可以接收到读写请求。所有的 Distro 协议的请
求场景主要分为三种情况:
1. 当该节点接收到属于该节点负责的实例的写请求时,直接写入。
2. 当该节点接收到不属于该节点负责的实例的写请求时,将在集群内部路由,转发给对应的节点,
从而完成读写。
3. 当该节点接收到任何读请求时,都直接在本机查询并返回(因为所有实例都被同步到了每台机
器上)。
Distro 协议作为 Nacos 的内嵌临时实例⼀致性协议,保证了在分布式环境下每个节点上面的服务
信息的状态都能够及时地通知其他节点,可以维持数十万量级服务实例的存储和⼀致性。