Nacos架构与原理深度分析

Posted Doker 多克

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nacos架构与原理深度分析相关的知识,希望对你有一定的参考价值。

Nacos 生态

   Nacos 几乎支持所有主流语言, 其中 Java/Golang/Python 已经支持 Nacos 2.0 长链接协议, 能
最大限度发挥 Nacos 性能。 阿里微服务 DNS(Dubbo+Nacos+Spring-cloud-alibaba/Seata/
Sentinel) 最佳实践, 是 Java 微服务生态最佳解决方案; 除此之外, Nacos 也对微服务生态活跃
的技术做了无缝的支持, 如目前比较流行的 Envoy、 Dapr 等, 能让用户更加标准获取微服务能力。
生态仓库: https://github.com/nacos-group

Nacos 优势

易⽤: 简单的数据模型, 标准的 restfulAPI, 易用的控制台, 丰富的使用文档。
稳定: 99.9% 高可用, 脱胎于历经阿里巴巴 10 年生产验证的内部产品, 支持具有数百万服务的大
规模场景, 具备企业级 SLA 的开源产品。
实时: 数据变更毫秒级推送生效; 1w 级, SLA 承诺 1w 实例上下线 1s, 99.9% 推送完成; 10w
级, SLA 承诺 1w 实例上下线 3s, 99.9% 推送完成; 100w 级别, SLA 承诺 1w 实例上下线 9s
99.9% 推送完成。
规模: 十万级服务/配置, 百万级连接, 具备强大扩展性。

设计原则

  •  极简原则, 简单才好用, 简单才稳定, 简单才易协作。
  • 架构⼀致性, ⼀套架构要能适应开源、 内部、 商业化(公有云及专有云) 3 个场景。
  • 扩展性, 以开源为内核, 商业化做基础, 充分扩展, 方便用户扩展。
  • 模块化, 将通用部分抽象下沉, 提升代码复用和健壮性。
  • 长期主义, 不是要⼀个能支撑未来 3 年的架构, 而是要能够支撑 10 年的架构。
  • 开放性, 设计和讨论保持社区互动和透明, 方便大家协作。

架构图
 整体架构分为用户层、 业务层、 内核层和插件, 用户层主要解决用户使用的易用性问题, 业务层主要解决服务发现和配置管理的功能问题, 内核层解决分布式系统⼀致性、 存储、 高可用等核心问题,插件解决扩展性问题。

 用户层

  •  OpenAPI: 暴露标准 Rest 风格 HTTP 接口, 简单易用, 方便多语言集成。
  • Console: 易用控制台, 做服务管理、 配置管理等操作。
  •  SDK: 多语言 SDK, 目前几乎支持所有主流编程语言。
  • Agent: Sidecar 模式运行, 通过标准 DNS 协议与业务解耦。
  •  CLI: 命令行对产品进行轻量化管理, 像 git ⼀样好用。

业务层

  • 服务管理: 实现服务 CRUD, 域名 CRUD, 服务健康状态检查, 服务权重管理等功能。
  • 配置管理: 实现配置管 CRUD, 版本管理, 灰度管理, 监听管理, 推送轨迹, 聚合数据等功能。
  •  元数据管理: 提供元数据 CURD 和打标能力, 为实现上层流量和服务灰度非常关键

 内核层

  •  插件机制: 实现三个模块可分可合能力, 实现扩展点 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 等问题。
  • 权限管理: 解决身份识别, 访问控制, 角色管理等问题

配置(Configuration)

配置管理 (Configuration Management)
 在 Nacos 中, 系统中所有配置的存储、 编辑、 删除、 灰度管理、 历史版本管理、 变更审计等所有与配置相关的活动统称为配置管理。
配置服务 (Configuration Service)
在服务或者应用运行过程中, 提供动态配置或者元数据以及配置管理的服务提供者。 
配置项(Configuration Item)
⼀个具体的可配置的参数与其值域, 通常以 param-key = param-value 的形式存在。 例如我们常
配置系统的日志输出级别(logLevel = INFO | WARN | ERROR) 就是⼀个配置项。
配置集(Configuration Set)
⼀组相关或者不相关的配置项的集合称为配置集。 在系统中, ⼀个配置文件通常就是⼀个配置集,包含了系统各个方面的配置。 例如, ⼀个配置集可能包含了数据源、 线程池、 日志级别等配置项。
命名空间(Namespace)
用于进行租户粒度的配置隔离。 不同的命名空间下, 可以存在相同的 Group 或 Data ID 的配置。
Namespace 的常用场景之⼀是不同环境的配置的区分隔离, 例如开发测试环境和生产环境的资源
(如数据库配置、 限流阈值、 降级开关) 隔离等。 如果在没有指定 Namespace 的情况下, 默认使用 public 命名空间。
配置组(Group)
Nacos 中的⼀组配置集, 是配置的维度之⼀。 通过⼀个有意义的字符串(如 ABTest 中的实验组、对照组) 对配置集进行分组, 从而区分 Data ID 相同的配置集。 当您在 Nacos 上创建⼀个配置时,如果未填写配置分组的名称, 则配置分组的名称默认采用 DEFAULT_GROUP 。 配置分组的常见场景: 不同的应用或组件使用了相同的配置项, 如 database_url 配置和 MQ_Topic 配置。
配置 ID(Data ID)
Nacos 中的某个配置集的 ID。 配置集 ID 是划分配置的维度之⼀。 Data ID 通常用于划分系统的配置集。 ⼀个系统或者应用可以包含多个配置集, 每个配置集都可以被⼀个有意义的名称标识。 DataID 尽量保障全局唯⼀, 可以参考 Nacos Spring Cloud 中的命名规则: $prefix-$spring.profiles.active-$file-extension
配置快照(Configuration Snapshot)
Nacos 的客户端 SDK 会在本地生成配置的快照。 当客户端无法连接到 Nacos Server 时, 可以使
用配置快照显示系统的整体容灾能力。 配置快照类似于 Git 中的本地 commit, 也类似于缓存, 会在适当的时机更新, 但是并没有缓存过期(expiration) 的概念

 配置资源模型

Namespace 的设计就是用来进行资源隔离的, 我们在进行配置资源的时候可以从以下两个角度来
看: 从单个租户的角度来看, 我们要配置多套环境的配置, 可以根据不同的环境来创建 Namespace 。比如开发环境、 测试环境、 线上环境, 我们就创建对应的 Namespace(dev、 test、 prod) ,Nacos 会自动生成对应的 Namespace Id 。 如果同⼀个环境内想配置相同的配置, 可以通过Group 来区分。 如下图所示:

从多个租户的角度来看, 每个租户都可以有自己的命名空间。 我们可以为每个用户创建⼀个命名空间, 并给用户分配对应的权限, 比如多个租户(zhangsan、 lisi、 wangwu) , 每个租户都想有⼀套自己的多环境配置, 也就是每个租户都想配置多套环境。 那么可以给每个租户创建⼀个 Namespace (zhangsan、 lisi、 wangwu) 。 同样会生成对应的 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 因为要支持多种服务类型的注册, 并能够具有机房容灾、 集群扩展等必不可少的能力, 在
1.0.0 正式支持 AP 和 CP 两种⼀致性协议并存。 1.0.0 重构了数据的读写和同步逻辑, 将与业务相关的 CRUD 与底层的⼀致性同步逻辑进行了分层隔离。 然后将业务的读写(主要是写, 因为读会直接使用业务层的缓存) 抽象为 Nacos 定义的数据类型, 调用⼀致性服务进行数据同步。 在决定使用 CP 还是 AP ⼀致性时, 使用⼀个代理, 通过可控制的规则进行转发。


负载均衡
在 Nacos 0.7.0 版本中, 我们除了提供基于健康检查和权重的负载均衡方式外, 还新提供了基于第三方 CMDB 的标签负载均衡器, 具体可以参考 CMDB 功能介绍文章。 使用基于标签的负载均衡器,目前可以实现同标签优先访问的流量调度策略, 实际的应用场景中, 可以用来实现服务的就近访问,当您的服务部署在多个地域时, 这非常有用。 使用这个标签负载均衡器, 可以支持非常多的场景,这不是本文要详细介绍的。 虽然目前 Nacos 里支持的标签表达式并不丰富, 不过我们会逐步扩展它支持的语法。 除此以外, Nacos 定义了 Selector, 作为负载均衡的统⼀抽象。 

 
健康检查
Nacos 目前支持临时实例使用心跳上报方式维持活性, 发送心跳的周期默认是 5 秒, Nacos 服务端会在 15 秒没收到心跳后将实例设置为不健康, 在 30 秒没收到心跳时将这个临时实例摘除。
Nacos 既支持客户端的健康检查, 也支持服务端的健康检查, 同⼀个服务可以切换健康检查模式。
我们认为这种健康检查方式的多样性非常重要, 这样可以支持各种类型的服务, 让这些服务都可以
使用到 Nacos 的负载均衡能力。 Nacos 下⼀步要做的是实现健康检查方式的用户扩展机制, 不管
Nacos 架构 < 74
是服务端探测还是客户端探测。 这样可以支持用户传入⼀条业务语义的请求, 然后由 Nacos 去执
行, 做到健康检查的定制

 高可用设计

  全局高可用:Nacos 部署架构上是单 Region 封闭, Region 间独立, 跨 Region 通过网关或者 Nacos-sync 完成服务互通。 从而降低 Region 间网络故障风险。

数据多级容灾:Nacos 持久化存储做了主备容灾, 而且底层存储数据多副本高可用保障。Nacos Server 有全量缓存数据, 即使存储挂或者不可用, 只影响写, 核心的读服务不受影响。Nacos SDK 有所需服务和配置缓存, Server 即使全挂, 走本地缓存, 保证核心业务调用不受影响。

同城容灾
Nacos 本身是采用 AP 的⼀致性模式, 同 Region 多个可用区部署, 任何⼀个可用区出问题, 剩下部分继续工作。很多人问为什么不是三个可用区呢? 因为业务都部署三个可用区从理论上是可用性最好的, 但是成本会大幅增加, 因此⼀般公司只选择两个可用区。

欢迎来到Doker,欢迎点赞和评论!或者加微信进入技术群聊!

Nacos 服务注册与发现原理分析

Nacos 另一个非常重要的特性就是服务注册与发现,说到服务的注册与发现相信大家应该都不陌生,在微服务盛行的今天,服务是非常重要的,而在 Nacos 中服务更被称为他的一等公民。

Nacos 支持几乎所有主流类型的 “服务” 的发现、配置和管理。

了解过 Dubbo 的同学,应该对 Dubbo 的架构非常熟悉,最经典的一张架构图如下所示:

图中的6个步骤的含义解释如下:

0、服务容器负责启动,加载,运行服务提供者。

1、服务提供者在启动时,向注册中心注册自己提供的服务。

2、服务消费者在启动时,向注册中心订阅自己所需的服务。

5、服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

其中图中最上方的 Registry 就是注册中心,负责服务的注册与发现。Dubbo 有自己的 Registry 实现,而 Nacos 则是另一种 Registry 的实现。

现在我们来了解下 Nacos 的服务注册与发现,首先在本地将 Nacos 服务端启动起来,具体怎么操作这里不再赘述,不清楚的同学可以参考我的其他文章。

模拟服务注册

我们模拟将同一个服务的两个实例注册到 Nacos 中,代码如下图所示:

Nacos 服务注册与发现原理分析

通过 NamingService 接口的 registerInstance 方法就可以将服务进行注册了,该方法有很多重载的方法,这里我们选择一个简单的来调用就好了。

注册完成后,通过调用 getAllInstances 方法,立即获取所有可用的实例,然后让主线程等待,打印如下:

Nacos 服务注册与发现原理分析

从打印结果中可以发现 naming 客户端成功获取到了两个实例。

模拟服务发现

服务注册之后,服务的消费者就可以向注册中心订阅自己所需要的服务了,注册中心会将所有服务的实例“推送”给消费者,这里我在推送上打了引号,原因是实际上获取服务是客户端主动轮询的,跟客户端获取配置中心的配置项的原理一样。这里不进行具体的描述,有兴趣的可以跟一下代码就知道了。

现在我创建一个服务消费者,然后向注册中心订阅一个服务,当接收到注册中心返回的服务列表之后,执行5次 select 服务实例的操作,相当于进行一个模拟的服务请求,具体的代码如下图所示:

Nacos 服务注册与发现原理分析

其中的 printInstances 方法主要是打印出所有服务的实例,为了节省篇幅就不写出来了,将 ServiceConsumer 类启动之后,打印出如下的日志:

Nacos 服务注册与发现原理分析

消费者每次获取一个健康的实例进行调用,接下来我就来分析下整个服务注册与发现的过程和大致的设计原理和思路。

服务如何注册

服务注册最重要的就是将服务注册到哪里,在注册中心服务端,肯定有一个用来管理服务的容器,他保存着所有服务的实例。

我们暂时不需要知道该容器具体的实现细节,只需要知道有这样一个概念。

Nacos 服务注册与发现原理分析
Nacos 服务注册与发现原理分析
Nacos 服务注册与发现原理分析
Nacos 服务注册与发现原理分析

服务如何发现

服务注册到注册中心后,服务的消费者就可以进行服务发现的流程了,消费者可以直接向注册中心发送获取某个服务实例的请求,这种情况下注册中心将返回所有可用的服务实例给消费者,但是一般不推荐这种情况。另一种方法就是服务的消费者向注册中心订阅某个服务,并提交一个监听器,当注册中心中服务发生变更时,监听器会收到通知,这时消费者更新本地的服务实例列表,以保证所有的服务均是可用的。

Nacos 服务注册与发现原理分析
Nacos 服务注册与发现原理分析

负载均衡

负载均衡有很多种实现方式,包括轮询法,随机方法法,对请求ip做hash后取模等等,从负载的维度考虑又分为:服务端负载均衡和客户端负载均衡。

Nacos 的客户端在获取到服务的完整实例列表后,会在客户端进行负载均衡算法来获取一个可用的实例,模式使用的是随机获取的方式。

Nacos 服务注册与发现原理分析

Nacos 服务注册与订阅的完整流程

Nacos 客户端进行服务注册有两个部分组成,一个是将服务信息注册到服务端,另一个是像服务端发送心跳包,这两个操作都是通过 NamingProxy 和服务端进行数据交互的。

Nacos 客户端进行服务订阅时也有两部分组成,一个是不断从服务端查询可用服务实例的定时任务,另一个是不断从已变服务队列中取出服务并通知 EventListener 持有者的定时任务。

官方提供的demo具有一定的迷惑性,不过这能迫使你去了解事物的本质。

你如果直接官方的demo,你会发现如下有趣的情况:

1、第一次注册了两个实例,获取实例时返回的是2个

2、然后解除注册其中的一个实例,再次获取实例时返回的还是2个

3、订阅服务的监听器将会收到两次 onEvent 回调,第一次是2个实例,第二次是1个实例

按照正常的情况,注册了两个实例,然后解除注册了一个只会,再次获取实例应该返回1个实例才对,但是返回了2个。

深入了解下源码就能知道原因:

客户端将获取到的服务实例保存在一个 map 中,而该 map 中的内容是由调度任务定时去更新的,存在一定的延时。


推荐阅读





如果文章对你有帮助,欢迎转发支持

如果觉得好看,请点击“在看” ↓↓

以上是关于Nacos架构与原理深度分析的主要内容,如果未能解决你的问题,请参考以下文章

微服务架构 *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)

微服务架构 | *2.5 Nacos 长轮询定时机制的源码分析

微服务架构 *2.5 Nacos 长轮询定时机制的源码分析

nacos原理

Nacos架构与原理深度分析

微服务架构 | *3.5 Nacos 服务注册与发现的源码分析