Nacos内核设计之一致性协议

Nacos一致性协议

Nacos技术架构

先简单介绍下Nacos的技术架构 从而对nacos有一个整体的认识 如图Nacos架构分为四层 用户层、应用层、核心层、各种插件 再深入分析下nacos一致性协议的发展过程及原理实现

为什么nacos需要一致性协议

Nacos是一个需要存储数据的一个组件 为了实现这个目标,就需要在Nacos内部实现数据存储 单机下其实问题不大,简单的内嵌关系型数据库即可 但是集群模式下 就需要考虑如何保障各个节点之间的数据一致性以及数据同步 而要解决这个问题 就不得不引入共识算法 通过算法来保障各个节点之间的数据的一致性

为什么Nacos会在单个集群中同时运行CP协议以及AP协议呢

  • 从服务注册来看

服务之间感知对方服务的当前可正常提供服务的实例信息,必须从服务发现注册中心进行获取,因此对于服务注册发现中心组件的可用性,提出了很高的要求,需要在任何场景下,尽最大可能保证服务注册发现能力可以对外提供服务

服务A和服务B往注册中心注册 服务A从注册中心获取服务B的信息 访问服务B 服务B从注册中心获取服务A的信息 访问服务A

如果数据丢失的话,是可以通过心跳机制快速弥补数据丢失

  • 对于非持久化数据(即需要客户端上报心跳进行服务实例续约)

不可以使用强一致性共识算法

必须要求集群内超过半数的节点正常 整个集群才可以正常对外提供服务

最终一致共识算法

最终一致共识算法的话,更多保障服务的可用性,并且能够保证在一定的时间内各个节点之间的数据能够达成一致

  • 对于持久化数据

强一致性

因为所有的数据都是直接使用调用Nacos服务端直接创建,因此需要由Nacos保障数据在各个节点之间的强一致性,故而针对此类型的服务数据,选择了强一致性共识算法来保障数据的一致性

nacos集群包括3个节点 分别位于不同的网络分区 服务A往节点1上注册 然后持久化到节点1对应的本地存储上 此时要保证节点1对应的持久化数据必须同步到节点2和节点3上

  • 从配置管理来看

    强一致性

在nacos服务端修改了配置 必须同步到集群中的大部分节点即必须要求集群中的大部分节点是强一致性的

强制一致性算法的选择

Nacos选择了JRaft 是因为JRaft支持多RaftGroup,为Nacos后面的多数据分片带来了可能

最终一致性算法的选择

最终一致性协议算法 例如Gossip和Eureka内的数据同步算法 而Nacos使用的是阿里自研的Distro算法 该算法集中了Gossip和Eureka同步算法的优势

Gossip缺点

对于原生的Gossip,由于随机选取发送消息的节点,也就不可避免的存在消息重复发送给同一节点的情况,增加了网络的传输的压力,也给消息节点带来额外的处理负载

Distro

引入了Server的概念 每个节点负责一部分数据以及将自己的数据同步给其他节点,有效的降低了消息冗余的问题

早期的Nacos一致性协议

早期Nacos版本架构

服务注册和配置管理一致性协议是分开的,没有下沉到Nacos的内核模块作为通用能力演进,服务发现模块一致性协议的实现和服务注册发现模块的逻辑强耦合在一起,并且充斥着服务注册发现的一些概念。这使得Nacos的服务注册发现模块的逻辑变得复杂且难以维护,耦合了一致性协议层的数据状态,难以做到计算存储彻底分离,以及对计算层的无限水平扩容能力也有一定的影响

解决该问题的思路

必然需要对Nacos的一致性协议做抽象以及下Nacos架构沉,使其成为Core模块的能力,彻底让服务注册发现模块只充当计算能力,同时为配置模块去外部数据库存储打下了架构基础

当前的Nacos架构

新架构已经完成了将一致性协议从原先的服务注册发现模块下沉到了内核模块当中,并且尽可能的提供了统一的抽象接口,使得上层的服务注册发现模块以及配置管理模块,不再需要耦合任何一致性语义,解耦抽象分层后,每个模块能快速演进,并且性能和可用性都大幅提升

Nacos是如何做到一致性下沉协议的呢

一致性协议最基础的两个方法就是读取数据(getData)、写入数据(write)

一致性协议已经被抽象在了consistency的包中,Nacos对于AP、CP的一致性协议接口使用抽象都在里面,并且在实现具体的一致性协议时,采用了插件可插拔的形式,进一步将一致性协议具体实现逻辑和服务注册发现、配置管理两个模块达到解耦的目的

两个计算模块耦合了带状态的接口

仅做完一致性协议抽象是不够的,如果只做到这里,那么服务注册发现以及配置管理,还是需要依赖一致性协议的接口,在两个计算模块中耦合了带状态的接口;并且,虽然做了比较高度的一致性协议抽象,服务模块以及配置模块却依然还是要在自己的代码模块中去显示的处理一致性协议的读写请求逻辑,以及需要自己去实现一个对接一致性协议的存储,这其实是不好的,服务发现以及配置模块,更多应该专注于数据的使用以及计算,而非数据怎么存储、怎么保障数据一致性,数据存储以及多节点一致的问题应该交由存储层来保证。为了进一步降低一致性协议出现在服务注册发现以及配置管理两个模块的频次以及尽可能让一致性协议只在内核模块中感知,Nacos这里又做了另一份工作——数据存储抽象

数据存储抽象

如果利用一致性协议实现一个存储,那么服务模块以及配置模块,就由原来的依赖一致性协议接口转变为了依赖存储接口,而存储接口后面的具体实现,就比一致性协议要丰富得多了,并且服务模块以及配置模块也无需为直接依赖一致性协议而承担多余的编码工作(快照、状态机实现、数据同步)。使得这两个模块可以更加的专注自己的核心逻辑

架构进一步演进-Nacos的计算层与存储层彻底分离

Nacos自研Distro协议

Distro协议是Nacos社区自研的一种AP分布式协议,是面向临时实例设计的一种分布式协议,其保证了在某些Nacos节点宕机后,整个临时实例处理系统依旧可以正常工作。作为一种有状态的中间件应用的内嵌协议,Distro保证了各个Nacos节点对于海量注册请求的统一协调和存储

设计思想

  • Nacos每个节点是平等的都可以处理写请求,同时把新数据同步到其他节点
  • 每个节点只负责部分数据,定时发送自己负责数据的校验值到其他节点来保持数据一致性
  • 每个节点独立处理读请求,及时从本地发出响应

Distro协议工作原理

数据初始化

新加入的Distro节点会进行全量数据拉取 具体操作是轮询所有的Distro节点,通过向其他的机器发送请求拉取全量数据

在全量拉取操作完成之后,Nacos的每台机器上都维护了当前的所有注册上来的非持久化实例数据

数据校验

在Distro集群启动之后,各台机器之间会定期的发送心跳。心跳信息主要为各个机器上的所有数据的元信息(之所以使用元信息,是因为需要保证网络中数据传输的量级维持在一个较低水平)每台机器在固定时间间隔会向其他机器发起一次数据校验请求

一旦在数据校验过程中,某台机器发现其他机器上的数据与本地数据不一致,则会发起一次全量拉取请求,将数据补齐

写操作

对于一个已经启动完成的Distro集群,在一次客户端发起写操作的流程中,当注册非持久化的实例的写请求打到某台Nacos服务器时,Distro集群处理的流程图如下

整个步骤包括几个部分(图中从上到下顺序)

  • 前置的Filter拦截请求,并根据请求中包含的IP和port信息计算其所属的Distro责任节点,并将该请求转发到所属的Distro责任节点上
  • 责任节点上的Controller将写请求进行解析
  • Distro协议定期执行Sync任务,将本机所负责的所有的实例信息同步到其他节点上

读操作

由于每台机器上都存放了全量数据,因此在每一次读操作中,Distro机器会直接从本地拉取数据。快速响应

这种机制保证了Distro协议可以作为一种AP协议,对于读操作都进行及时的响应。在网络分区的情况下,对于所有的读操作也能够正常返回;当网络恢复时,各个Distro节点会把各数据分片的数据进行合并恢复

小结

Distro协议是Nacos对于临时实例数据开发的一致性协议。其数据存储在缓存中,并且会在启动时进行全量数据同步,并定期进行数据校验

在Distro协议的设计思想下,每个Distro节点都可以接收到读写请求。所有的Distro协议的请求场景主要分为三种情况

  • 当该节点接收到属于该节点负责的实例的写请求时,直接写入
  • 当该节点接收到不属于该节点负责的实例的写请求时,将在集群内部路由,转发给对应的节点,从而完成读写
  • 当该节点接收到任何读请求时,都直接在本机查询并返回(因为所有实例都被同步到了每台机器上)

Distro协议作为Nacos的内嵌临时实例一致性协议,保证了在分布式环境下每个节点上面的服务信息的状态都能够及时地通知其他节点,可以维持数十万量级服务实例的存储和一致性

本文参考并借鉴<Nacos架构与原理>

Guess you like

Origin juejin.im/post/7049724173765214245