Nacos 简介

Nacos 起源于 2008 年阿里巴巴的五彩石项目,该项目完成了微服务拆分与业务中台建设。随着云计算和开源生态的快速发展,2018 年我们深刻感受到开源软件行业的巨大影响力,因此决定将 Nacos 正式开源,输出阿里巴巴在服务发现与配置管理领域十余年的技术沉淀,推动微服务体系发展,加快企业数字化转型进程。
目前,Nacos 支持主流微服务开发语言、主流服务框架以及配置管理框架,例如支持 Dubbo 和 SCA,同时也对接了部分云原生生态组件,如 CoreDNS 和 Sentinel 等。
在客户端语言支持方面,Nacos 已兼容 Java、Go、Python 等主流编程语言,也支持近期发布正式版本的 C# 和 C++。在此也感谢所有社区贡献者长期以来的支持与投入。

Nacos 开源两年多以来,累计发布了 34 个版本,其中包含多个关键里程碑版本。Nacos 仍然是一款年轻且持续演进的开源项目,未来还有很大的提升空间,欢迎社区开发者和各界技术伙伴共同参与建设。

Nacos 1.X 架构及问题
接下来我们来看一下 Nacos 1.X 的整体架构,并进一步分析其存在的一些关键问题。首先从架构简图开始说明。
Nacos 1.X 架构层次

Nacos 1.X 大体可以分为 5 个层次,分别是接入层、通信层、功能层、同步层和持久化层。
接入层是用户直接交互最频繁的部分,主要由 Nacos 客户端、依赖客户端的 Dubbo 和 SCA,以及用户操作的控制台 Console 组成。客户端与 Console 在进行服务注册、服务发现和配置管理时,统一通过 HTTP OpenAPI 发起通信请求。
通信层主要基于 HTTP 的短连接请求模型实现,部分推送能力则通过 UDP 完成通信。
功能层目前主要包括服务发现和配置管理,这一层也是实际承载服务治理与配置管理能力的核心业务层。
同步层包括用于数据同步的 AP 模式 Distro、CP 模式 Raft,以及最基础的水平通知 Notify,三者应用场景各不相同:
- Distro:用于非持久化服务的数据同步模式。
- Raft:用于持久化服务的同步模式,以及在使用 Derby 作为配置存储时同步配置操作。
- Notify:在使用 MySQL 作为配置存储时,通知其他节点更新缓存并触发配置推送。
持久化层中,Nacos 使用 MySQL、Derby 和本地文件系统来完成数据持久化。配置信息、用户信息和权限信息存储在 MySQL 或 Derby 数据库中;持久化服务信息以及服务、实例元数据信息则存储在本地文件系统中。
Nacos 1.X 架构下的服务模型
下面通过一次服务发现流程,进一步了解 Nacos 1.X 架构以及基于当前架构实现的 Nacos 服务发现模型。

Nacos 客户端在注册服务时,会通过 OpenAPI 发送 HTTP 注册请求,请求内容中会携带服务信息和实例信息。通常这一步由微服务框架 SCA 和 Dubbo 自动完成。
服务端收到请求后,会先在 Controller 中进行数据读取和校验,例如 IP 是否合法、服务名是否正确等。校验通过后,如果该服务是首次注册,Nacos 会在服务端创建一个 Service 对象,并将本次注册的实例信息写入该 Service 对象中;如果服务端已经存在该 Service 对象,则直接把新注册的实例信息加入其中。这个 Service 对象通过“命名空间 + Group + Service”的组合来保证唯一性。
在实例写入 Service 的同时,会触发两个事件。其中一个事件用于数据同步:Nacos 服务端会根据该服务是否为临时实例,选择使用 Distro 或 Raft 协议进行同步,通知其他 Nacos 节点该服务发生了变更;另一个事件则用于通知当前 Nacos 服务节点上已订阅该服务的订阅者,并根据订阅者信息通过 UDP 的方式,将最新服务列表推送到订阅客户端。至此,一次完整的服务注册流程便执行完成。
另外,对于被定义为持久化的服务,其全部信息都会通过 Raft 协议保证写入文件系统并被持久化存储。
最后,其他 Nacos 节点在通过数据同步感知到 Service 变更时,也会触发通知订阅者的事件,从而让在其他 Nacos 服务节点上订阅该服务的客户端同样收到最新推送。
1.X 架构存在的问题
粗略介绍完 Nacos 1.X 的架构和服务发现模型后,接下来分析一下 Nacos 1.X 架构面临的几个核心问题。
一句话总结就是:心跳多、无效查询多、心跳续约感知变化慢、连接消耗大、系统资源空耗严重。

- 心跳数量多,导致 TPS 居高不下
通过心跳进行实例续约后,当服务规模持续扩大,尤其是在 Dubbo 这类接口级服务较多的场景下,心跳请求以及配置元数据轮询请求数量会迅速增长,导致集群 TPS 持续处于高位,系统资源被大量无效消耗。
- 通过心跳续约感知服务变化,时延较长
依赖心跳续约来感知服务变化时,必须等到超时时间到达后才会移除实例并通知订阅者,默认值为 15 秒,整体时延较高、时效性较差。如果缩短超时时间,则在网络抖动场景下又容易频繁触发变更推送,进一步加大客户端和服务端的资源损耗。
- UDP 推送不可靠,导致 QPS 居高不下
由于 UDP 本身不具备可靠传输能力,因此客户端侧需要定期执行对账查询,以确保本地缓存的服务列表状态正确。当订阅客户端规模不断增大时,集群 QPS 会明显升高,但大多数服务列表并不会频繁变化,这就导致了大量无效查询,进而造成明显的资源浪费。
- 基于 HTTP 短连接模型,TIME_WAIT 状态连接过多
HTTP 短连接模型下,每次客户端请求都会创建并销毁一次 TCP 连接。TCP 连接销毁后会进入 TIME_WAIT 状态,在彻底释放之前仍需要一定时间。当 TPS 和 QPS 较高时,服务端和客户端可能积累大量 TIME_WAIT 状态连接,从而引发 connect timeout 或 Cannot assign requested address 等问题。
- 配置模块的 30 秒长轮询引起频繁 GC
配置模块使用基于 HTTP 短连接的阻塞模型来模拟长连接通信,但由于它并不是真正意义上的长连接,因此每 30 秒都需要进行一次请求及上下文切换。每一次切换都会带来一定的内存开销,进而导致服务端频繁触发 GC。
Nacos 2.0 架构及新模型
Nacos 2.0 架构层次
Nacos 2.X 在 1.X 架构基础上,新增了对长连接模型的支持,同时继续保留对旧版客户端和 OpenAPI 的核心兼容能力。

通信层目前通过 gRPC 和 RSocket 实现了长连接 RPC 调用与消息推送能力。
在服务端侧,新增了一层连接层,用于将来自不同客户端的不同类型 Request 请求统一转换为语义一致的功能数据结构,以便复用底层业务处理逻辑。同时,未来的流量控制、连接治理和负载均衡等能力,也会放在连接层中进行处理。
其他架构分层整体上保持不变。
Nacos 2.0 新服务模型
虽然 Nacos 2.0 在整体架构层次上变化不算特别大,但在具体服务模型细节上已经发生了明显调整。下面仍然通过服务注册流程,进一步了解 Nacos 2.0 服务模型的变化。

由于通信改为了 RPC 方式,因此同一个客户端的所有请求,无论是服务注册还是服务订阅,都会通过同一条连接并固定落到同一个服务节点上。这一点与之前基于 HTTP 的连接方式不同,在 1.X 中,每次请求都可能落在不同的 Nacos 节点上。由此,服务发现的数据模型也从原先的无状态模式,演变为与连接状态绑定的有状态数据。为了适应这一变化,Nacos 抽象出一个新的数据结构,用于关联同一客户端通过该连接发布和订阅的内容,这个结构被命名为 Client。这里的 Client 并不是单纯指客户端程序本身,而是代表与该客户端相关联的数据集合,一个连接对应一个 Client。
当客户端发布服务后,该客户端所发布的全部服务信息以及订阅信息,都会更新到与其连接对应的 Client 对象中,然后通过事件机制触发索引信息更新。这里的索引信息是客户端连接与服务之间的索引关系,便于快速聚合出需要推送的服务维度数据。
索引信息更新完成后,会继续触发推送事件。此时系统会将所有与该服务相关的 Client 对象,基于刚建立的索引关系进行聚合。当数据聚合完成后,再从客户端连接中筛选出订阅该服务的订阅者连接,并通过这些连接将推送数据返回给客户端。这样,一次服务发布变更的主链路就完成了。
再回到数据同步流程,当客户端发布服务时,实际被更新的对象从原先的 Service 变为了 Client,因此需要同步的内容也相应变成了 Client 对象;同时,服务端之间的通信方式也会切换为 RPC。这里需要注意的是,只有真正被客户端直接更新的 Client 对象才会触发同步;如果某个 Client 对象是通过节点间同步更新得到的,则不会再次触发重复同步。
最后再看 Metadata。Metadata 是从 1.X 版本中的 Service 对象和 Instance 对象中拆分出来的一部分属性,例如服务元数据 label 标签、实例上下线状态、权重以及实例元数据 label 标签等。这些元数据可以通过 OpenAPI 单独修改,并在数据聚合时生效。之所以将元数据从基础数据中拆分出来,是因为基础数据如 IP、端口、服务名等,一旦发布后原则上不应被修改,并且应以发布时的数据为准;而其他元数据,如上下线状态和权重,往往需要在运行过程中动态调整。因此将其拆分后,分别走两条不同的处理工作流,会更加合理。
Nacos 2.0 架构的优缺点
前面简要介绍了 Nacos 2.0 的架构以及新服务模型的工作方式,接下来分析一下这类架构升级带来的优点与不足。

优点
客户端不再需要定时发送实例心跳,只需通过 keepalive 消息维持连接可用即可,重复 TPS 可以大幅下降。
TCP 连接断开能够被更快感知,从而显著提升服务变化的响应速度。
基于长连接的流式推送,相比 UDP 更加可靠;同时 NIO 机制具备更高吞吐能力,并且由于推送可靠性增强,客户端对账服务列表的时间间隔可以进一步拉长,甚至可以取消部分相关请求,重复和无效 QPS 可明显降低。
长连接模型可以避免频繁建连和断连带来的额外开销,从而大幅缓解 TIME_WAIT 问题。
真实长连接的引入,也有效解决了配置模块频繁 GC 的问题。
同步内容更细粒度,能够减少 Nacos 服务节点之间的通信压力。
缺点
没有绝对完美的方案,新架构在带来收益的同时,也会引入一些新的挑战:
内部结构复杂度提升,需要额外管理连接状态以及连接层面的负载均衡能力。
数据从原先的无状态模型转变为与连接绑定的有状态数据,整体流程链路会更长。
RPC 协议在可观测性方面通常不如 HTTP 直观。即使 gRPC 是基于 HTTP2.0 Stream 实现,排障与观测体验仍然不如直接使用 HTTP 协议简单明了。
Nacos 2.X 规划
接下来简单介绍一下 Nacos 2.X 的后续规划,主要包括文档建设、质量提升以及 Roadmap 三个方面。
在文档和质量方面,Nacos 1.X 的表现并不算理想。文档内容相对较少,主要停留在基础使用层面;同时与版本存在一定脱节,更新也不够及时;对底层技术内容的说明不够充分,导致参与社区贡献的门槛偏高。代码质量和测试质量也仍有提升空间,虽然已经通过 checkstyle 进行 code style 校验,并启用了社区协作 review 机制,但这些措施仍远远不够。Nacos 2.X 将持续更新并细化官网使用文档;通过电子书形式解析关键技术细节;借助 GitHub 展示技术方案,促进社区讨论与贡献;同时还会对代码进行大规模重构,加强 UT 和 IT 治理,并在未来开源 Benchmark,方便开源用户进行性能压测。

而在 RoadMap 方面,Nacos 2.X 将对项目进行较大幅度的重构,完成初步插件化建设,并针对前面提到的 2.0 架构中的一些不足,例如负载均衡和可观测性等问题进行进一步增强。
加入我们
欢迎大家在 Nacos GitHub 上提交 Issue 和 PR,一起参与讨论与贡献,也欢迎加入 Nacos 社区群,参与更多开源社区交流。
除了参与开源项目建设,我们也欢迎更多有能力、有意愿的同学加入阿里云,共同建设云原生生态。详情可查看云原生应用平台相关信息。
本文整理自 Spring Cloud Alibaba Meetup 杭州站演讲,点击此处立即参与 1 月 9 日下周六的上海站 Meetup,聆听 Nacos 用户分享 Nacos 在知名互联网教育公司的落地实践与应用经验。
作者简介
杨翊,花名席翁,Nacos PMC,主要参与服务发现模块,并负责部分内核重构与性能提升工作。同时也是 Apache ShardingSphere PMC,主要负责和参与过的模块包括路由模块、分布式事务、数据同步以及弹性扩容等。
