Nacos体系架构:什么是服务治理?
什么是服务治理
首先,让我通过一个例子告诉你服务治理解决了什么问题。
我的系统包含两个微服务(服务 A 和服务 B),每一个微服务有 10 个虚拟节点,两个服务组成了一个 20 台虚拟机的微服务集群。如果此时微服务 A 想要调用微服务 B,我们怎么来发起这个调用呢?
一种通用做法是:在服务 A 的配置文件中添加一个指向服务 B 的地址,但这个地址并不直接指向任何一台服务 B 集群中的节点,而是指向一个 VIP(虚拟 IP 地址)或者是一个网关。这个 VIP 或网关背后维护了 B 集群的服务节点列表,VIP 层通过负载均衡策略再将请求转到后面配置的某一台服务器。我画了一幅图来描述这个服务调用过程。
从上面的图中我们可以看出,服务 A 与服务 B 之间互相不直接通信,服务调用完全依靠 VIP 作为中间人来完成。我们如果想要为服务集群扩容或缩容,必须将服务器配置到对应的 VIP 地址上。如果你的应用是一个由数百个微服务组成的大型应用,光是管理这些 VIP Pool 的人力成本就够网络运维团队喝上一壶了。
那在微服务架构中,怎么才能实现一种简单可靠的远程服务调用,不让 VIP 中间商赚差价呢?这就要说到我们的服务治理理论了。
服务治理初探
如果我们要解决中间商赚差价的问题,那么最好的办法就是让双方直连。因此,服务治理要解决的首要任务就是**服务注册**与**服务发现**,通过这两项技术,我们就能让微服务之间发起面对面的直接调用。
那么服务 A 怎么知道服务 B 中每台机器的地址呢?为了让服务 A 拿到服务 B 的机器清单,我们需要搭建一个中心化的服务注册中心,服务 B 只要将自己的信息添加到注册中心里,服务 A 就能够从注册中心获取到服务 B 的所有节点列表。我画了一张图来帮助你更好地理解这个过程。
从上图中的步骤中我们可以看出,首先,服务 B 集群向注册中心发起了注册,将自己的地址信息上报到注册中心,这个过程就是**服务注册**。接下来,每隔一段时间,服务 A 就会从服务中心获取服务 B 集群的服务列表,或者由服务中心将服务列表的变动推送给服务 A,这个过程叫做**服务发现**;最后,服务 A 根据本地负载均衡策略,从服务列表中选取某一个服务 B 的节点,发起服务调用。
在这个过程中,**注册中心的角色是一个中心化的信息管理者**,所有的微服务节点在启动后都会将自己的地址信息添加到注册中心。在服务注册的过程中,有两个关键信息是最为重要的,我把它们列在了这里。
- 服务名称:服务名称通常默认是 spring.application.name 属性,在服务注册过程中我们必须将应用服务名上报到注册中心,这样其他服务才能根据服务名称找到对应的服务节点列表;
- 地址信息:包括服务节点的 IP 地址和端口。
通过上面这两个信息,调用方就能精准定位到目标微服务。除此之外,服务注册请求中还包含一些额外的注册信息,我将在 Nacos 的实战环节为你详细讲解这些注册参数。
通过服务注册和服务发现,我们已经能够实现端到端的服务调用链路,但这个方案似乎还并不完善,因为它缺少了异常容错的机制。
如果服务 B 集群因为未知的网络故障导致无法响应服务,这时候服务 A 向服务 B 发起了服务调用,就会发生超时或者服务无响应的异常情况。那我们如何在服务治理方案中规避这类问题呢?
业界通用的解决方案是“heathcheck”或者“heartbeat”,又叫“服务探活”或“心跳检查”。注册中心可以通过这种机制来标记异常服务,这样一来,Client 端在发送服务请求的时候就能避开异常节点。
我将这些异常处理的步骤添加到了服务注册流程中,并画了一个完整的微服务生命周期的图,你可以参考一下。
看了图片,你可能会问,怎么还有一个“服务剔除”呢?它是怎么实现的?先说一个大前提。所有的服务都要在注册中心进行注册,而且每个节点都需要每隔一段时间向注册中心同步自己当前的状态,我们很形象地称这个过程为 heartbeat(心跳)。
如果节点持续发送心跳信息,则一切正常,服务可以被发现;如果注册中心在一段时间内没有收到 Client 的心跳包,注册中心就会将这个节点标记为下线状态,进而将该服务从服务列表中剔除。这里我再补充一句,我们上面说的“服务剔除”是由注册中心主导的“被动下线”场景。
除此之外还有一类服务“主动下线”的场景,也就是当服务节点关闭或者重启的时候,通过发送一条“服务下线”指令给到注册中心,将当前节点标记为下线状态。到这里,相信你已经完全理解了微服务生命周期各个状态间的流转,也知道了服务注册中心在微服务生命周期中扮演了什么角色。
接下来,我们来了解 Spring Cloud 中的服务注册中心 Nacos。
Nacos 体系架构
Nacos 有三个核心知识点:领域模型、数据模型和基本架构,这是我们整体把握 Nacos 架构的关键。下面我们来依次看看。
领域模型
Nacos 领域模型描述了服务与实例之间的边界和层级关系。Nacos 的服务领域模型是以“服务”为维度构建起来的,这个服务并不是指集群中的单个服务器,而是指微服务的服务名。
“服务”是 Nacos 中位于最上层的概念,在服务之下,还有集群和实例的概念。为了方便你理解这三者的层级关系,我画了一张图,你可以参考一下。
从上面的图中你可以看出,Nacos 的服务领域模型从上到下分为了服务、集群和实例三层,我分别介绍一下这三个层次所包含的重要数据内容。
- 服务
在服务这个层级上我们可以配置元数据和服务保护阈值等信息。服务阈值是一个 0~1 之间的数字,当服务的健康实例数与总实例的比例小于这个阈值的时候,说明能提供服务的机器已经没多少了。这时候 Nacos 会开启服务保护模式,不再主动剔除服务实例,同时还会将不健康的实例也返回给消费者。尽管这样做可能造成请求失败,但间接保证了最低限度的服务可用性。 - 集群
一个服务由很多服务实例组成,在每个服务实例启动的时候,我们可以设置它所属的集群,在集群这个层级上,我们也可以配置元数据。
除此之外,我们还可以为持久化节点设置健康检查模式。所谓持久化节点,是一种会保存到 Nacos 服务端的实例,即便该实例的客户端进程没有在运行,实例也不会被服务端删除,只不过 Nacos 会将这个持久化节点状态标记为不健康,Nacos 可以采用一种“主动探活”的方式来对持久化节点做健康检查。
除了持久化节点以外,大部分服务节点在 Nacos 中以“临时节点”的方式存在,它是默认的服务注册方式,从名字中我们就可以看出,这种节点不会被持久化保存在 Nacos 服务器,临时节点通过主动发送 heartbeat 请求向服务器报送自己的状态。 - 实例
这里所说的实例就是指服务节点,我们可以在 Nacos 控制台查看每个实例的 IP 地址和端口、编辑实例的元数据信息、修改它的上线 / 下线状态或者配置路由权重等等。
你会发现,在这三个层级上都有“元数据”这一数据结构,你可以把它理解为一组包含了服务描述信息(如服务版本等)和自定义标签的数据集合。Client 端通过服务发现技术可以获取到每个服务实例的元数据,你可以将自定义的属性加入到元数据并在 Client 端实现某些定制化的业务场景。
了解了领域模型之后,你知道服务调用的发起方是如何定位到领域模型中的服务实例的吗?这就要说起 Nacos 的数据模型了。
数据模型
数据模型Nacos 的数据模型有三个层次结构,分别是 Namespace、Group 和 Service/DataId,我画了一幅图,帮你理解这三个层次之间的包含关系:
从上图中你可以看出,Namespace、Group 和 Service/DataId 是一个依次包含的结构,我分别对每一层做一个简单介绍。
- Namespace:即命名空间,它是最顶层的数据结构,我们可以用它来区分开发环境、生产环境等不同环境。默认情况下,所有服务都部署到一个叫做“public”的公共命名空间;
- Group:在命名空间之下有一个分组结构,默认情况下所有微服务都属于“DEFAULT_GROUP”这个分组,不同分组间的微服务是相互隔离的;
- Service/DataID:在 Group 分组之下,就是具体的微服务了,比如订单服务、商品服务等等。
通过 Namespace + Group + Service/DataID,我们就可以精准定位到一个具体的微服务。比如,我想调用生产环境下 A 分组的订单服务,那么对应的服务寻址的 Key 就是类似 Production.A.orderService 的组合。
了解了 Nacos 的数据模型之后,我再来带你看一下 Nacos 的基本架构,这样你就对 Nacos 的功能模块有一个更全面的认识。
Nacos 基本架构
Nacos 的核心功能有两个,一个是 Naming Service,也就我们用来做服务发现的模块;另一个是 Config Service,用来提供配置项管理、动态更新配置和元数据的功能,配置中心的先不做介绍。
我这里用一张 Nacos 社区的基本架构图来作为示例,带你看一下 Nacos 在功能模块层面的基本架构。
从上面的图中你可以看出,Provider APP 和 Consumer APP 通过 Open API 和 Nacos 服务器的核心模块进行通信。这里的 Open API 是一组对外暴露的 RESTful 风格的 HTTP 接口。如果你对 Open API 里具体的接口感兴趣,可以从Nacos 官方网站获取更多的关于 Open API 的详细信息。
在 Nacos 和核心模块里,Naming Service 提供了将对象和实体的“名字”映射到元数据的功能,这是服务发现的基础功能之一。例如,我想要调用 OrderService,我手里有这个服务的 Namespace 和 Group 信息,那么我就可以通过 Naming Service 定位到这个服务对应的实例列表。同理,如果我有一个 DNS 名称,同样可以借助 Naming Service 获取 DNS 背后配置的 IP 列表。以上两个场景就分别对应了服务发现和 DNS 功能,这两个场景都是 Naming Service 的核心场景。
Nacos 还有一个相当重要的模块:Nacos Core 模块。它可以提供一系列的平台基础功能,是支撑 Nacos 上层业务场景的基石。我挑选了几个 Nacos Core 中包含的重要功能,你可以看一下。
除了 Nacos Core 提供的这些功能以外,Nacos 还有一个“一致性协议”,用来确保 Nacos 集群中各个节点之间的数据一致性。Nacos 内部支持两种一致性协议,
- 一种是侧重一致性的 Raft 协议,基于集群中选举出来的 Leader 节点进行数据写入;
- 另一种是针对临时节点的 Distro 协议,它是一个侧重可用性(或最终一致性)的分布式一致性协议。
到这里,以上就是 Nacos 的基本架构部分。