Nacos简介—4.Nacos架构和原理一(nacos 架构)
大纲
1.Nacos的定位和优势
2.Nacos的整体架构
3.Nacos的配置模型
4.Nacos内核设计之一致性协议
5.Nacos内核设计之自研Distro协议
6.Nacos内核设计之通信通道
7.Nacos内核设计之寻址机制
8.服务注册发现模块的注册中心的设计原理
9.服务注册发现模块的注册中心的服务数据模型
10.服务注册发现模块的健康检查机制
11.配置管理模块的配置一致性模型
12.Zookeeper、Eureka和Nacos的对比总结
整理于官方文档
1.Nacos的定位和优势
(1)Nacos的定位
(2)Nacos的优势
(3)Nacos生态
(1)Nacos的定位
Nacos是Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的服务发现和管理和动态配置管理的平台。
(2)Nacos的优势
一.易用
简单的数据模型,标准的restfulAPI,易用的控制台,丰富的使用文档。
二.稳定
99.9%高可用,支持具有数百万服务的大规模场景。
三.实时
数据变更毫秒级推送生效。1w级,SLA承诺1w实例上下线1s,99.9%推送完成。10w级,SLA承诺1w实例上下线3s,99.9%推送完成。100w级别,SLA承诺1w实例上下线9s,99.9%推送完成。
四.规模
十万级服务/配置,百万级连接,具备强大扩展性。
(3)Nacos生态
Nacos几乎支持所有主流语言,比如Java/Python/Golang。这三种语言已经支持Nacos 2.0长链接协议,能最大限度发挥Nacos性能。DNS(Dubbo + Nacos +
Spring-Cloud-Alibaba/Seata/Sentinel)最佳实践,是Java微服务生态最佳解决方案。
2.Nacos的整体架构
(1)架构图
(2)用户层
(3)业务层
(4)内核层
(5)插件
(1)架构图
整体架构分为用户层、业务层、内核层和插件。用户层主要解决用户使用的易用性问题,业务层主要解决服务发现和配置管理的功能问题,内核层用来解决分布式系统一致性、存储、高可用等核心问题,插件用来解决扩展性问题。
(2)用户层
一.OpenAPI
暴露Rest风格的HTTP接口,简单易用和方便集成。
二.Console
易用控制台,用于服务管理、配置管理等操作。
三.SDK
多语言SDK,目前几乎支持所有主流编程语言。
四.Agent
Sidecar模式运行,通过标准DNS协议与业务解耦。
五.CLI
命令行对产品进行轻量化管理,像git一样好用。
(3)业务层
一.服务管理
实现服务CRUD、域名CRUD、服务健康状态检查、服务权重管理等功能。
二.配置管理
实现配置CRUD、版本管理、灰度管理、监听管理、推送轨迹等功能。
三.元数据管理
提供元数据CURD和打标能力,为实现上层流量和服务灰度非常关键。
(4)内核层
一.插件机制
实现三个模块可分可合,实现扩展点SPI机制,用于扩展自己公司定制。
二.事件机制
实现异步化事件通知、SDK数据变化异步通知等逻辑,这是Nacos高性能的关键部分。
三.日志模块
管理日志分类、日志级别、日志移植、日志格式、异常码 + 帮助文档。
四.回调机制
SDK通知数据,通过统一的模式回调用户处理,接口和数据结构需要具备可扩展性。
五.寻址机制
解决Server IP直连、域名访问、NameServer寻址、广播等多种寻址模式。
六.传输通道
解决Server与存储、Server间、Server与SDK间高效通信问题。
七.容量管理
管理每个租户、分组下的容量,防止存储被写爆,影响服务可用性。
八.流量管控
按照租户、分组等维度管控请求频率、长链接个数、报文大小等。
九.缓存机制
容灾目录、本地缓存、Server缓存机制,是Nacos高可用的关键。
十.启动模式
按照单机模式、配置模式、服务模式、DNS模式,启动不同的模块。
十一.一致性协议
解决对于不同数据的不同一致性要求的问题。
十二.存储模块
解决数据持久化、非持久化存储,数据分片问题。
(5)插件
一.NameServer
解决Namespace到ClusterID的路由问题。
解决用户环境与Nacos物理环境映射问题。
二.CMDB
解决元数据存储,与三方CMDB系统对接问题。
解决应用、人、资源关系。
三.Metrics
暴露标准Metrics数据,方便与三方监控系统打通。
四.Trace
暴露标准Trace,方便与SLA系统打通。日志白平化 + 推送轨迹能力,方便和计量计费系统打通。
五.接入管理
相当于阿里云开通服务,分配身份、容量、权限。
六.用户管理
解决用户管理、登录、SSO等问题。
七.权限管理
解决身份识别,访问控制,角色管理等问题。
八.审计系统
扩展接口方便与不同公司审计系统打通。
九.通知系统
核心数据变更、操作,方便打通SMS系统,通知对应人数据发生变更。
3.Nacos的配置模型
(1)修改服务配置的背景
(2)Nacos配置管理中的概念
(3)Nacos的配置模型之基础模型
(4)Nacos的配置模型之配置资源模型
(5)Nacos的配置模型之配置存储模型(ER图)
(1)修改服务配置的背景
在单体架构时,我们可以将配置写在配置文件中,但缺点就是每次修改配置都需要重启服务才能生效。
当应用程序实例比较少的时候还可以维护。如果转向微服务架构成百上千实例,每修改一次配置要将全部实例重启,这样不仅增加了系统的不稳定性,也提高了维护的成本。
那么如何能够做到服务不重启就可以修改配置?所以就产生了四个基础诉求:
一.需要支持动态修改配置
二.需要实时的动态变更
三.变更太快如何管控变更风险,如灰度、回滚等
四.敏感配置如何做安全配置
(2)Nacos配置管理中的概念
一.配置(Configuration)
开发中通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在,让系统更好地适配实际的物理运行环境。
配置管理一般在系统部署时进行,由系统管理员或运维人员完成该步骤。配置变更是调整系统运行时的行为的有效手段之一。
二.配置管理(Configuration Management)
在Nacos中,系统中所有配置的存储、编辑、删除、灰度管理、历史版本管理、变更审计等所有与配置相关的活动统称为配置管理。
三.配置服务(Configuration Service)
在服务或应用运行中,提供动态配置或元数据以及配置管理的服务提供者。
四.配置项(Configuration Item)
一个具体的可配置的参数与其值,通常以"key = value"的形式存在。例如我们常配置系统的日志输出级别"logLevel = INFO"就是一个配置项。
五.配置集(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通常用于划分系统的配置集,一个系统或应用可包含多个配置集。每个配置集都可被一个有意义的名称标识。Data ID尽量保障全局唯一,可以参考Nacos Spring Cloud中的命名规则。
九.配置快照(Configuration Snapshot)
Nacos的客户端SDK会在本地生成配置的快照。当客户端无法连接Nacos时,可使用配置快照显示系统的整体容灾能力。配置快照类似于Git中的本地commit,也类似于缓存。配置快照会在适当的时机更新,但是并没有缓存过期(expiration)的概念。
(3)Nacos的配置模型之基础模型
下图是Nacos配置管理的基础模型:
说明一:Nacos提供了可视化的控制台,通过控制台可以发布配置、更新配置、删除配置、灰度发布、版本管理等。
说明二:Client端也可以发布配置、更新配置、监听配置等。
说明三:Client端会通过gRPC长连接监听配置。
说明四:Server端会对比Client端配置的MD5和本地配置的MD5是否相等,如果不相等,那么Server端就会推送配置变更到Client端。
说明五:Client端会保存配置的快照,当服务端出现问题时则从本地获取。
(4)Nacos的配置模型之配置资源模型
Namespace的设计就是用来进行资源隔离的,我们在进行配置资源时可以从以下两个角度来看。
角度一:从单个租户的角度来看
我们要配置多套环境的配置,可以根据不同的环境来创建Namespace。比如有开发环境、测试环境、线上环境,那么就创建对应的dev、test、prod。Nacos会自动生成对应的Namespace Id。如果同一个环境内想配置不相同的配置,可以通过Group来区分。
角度二:从多个租户的角度来看
每个租户都可以有自己的命名空间。我们可以为每个用户创建一个命名空间,并给用户分配对应的权限。
比如多个租户:zhangsan、lisi、wangwu。每个租户都想有一套自己的多环境配置,即每个租户都想配置多套环境。那么可以给每个租户创建一个Namespace:zhangsan、lisi、wangwu。同样会生成对应的Namespace Id,然后使用Group区分不同环境的配置。如下图所示:
(5)Nacos的配置模型之配置存储模型(ER图)
Nacos存储配置有几个比较重要的表:
一.config_info表存储配置信息
里面有dataId、groupId、content、tenantId、encryptedDataKey等数据。
二.config_info_beta表存储灰度测试的配置信息
存储的字段内容和config_info基本相似,但有一个beta_ips字段是用于客户端请求配置时,判断是否是灰度的IP的。
三.config_tags_relation表存储配置的标签
在发布配置时如果指定了标签,则会把标签和配置的关联信息存储在该表。
四.his_config_info表存储配置的历史信息
对配置进行的发布、更新、删除等操作都会记录一条数据,可以根据这个标实现多版本管理和快速回滚。
4.Nacos内核设计之一致性协议
(1)为什么Nacos需要一致性协议
(2)为什么Nacos会同时运行CP协议以及AP协议
(3)为什么Nacos选择Raft以及Distro协议
(4)Nacos早期的一致性协议
(5)Nacos当前的一致性协议
(6)Nacos如何做到一致性协议下沉的
(1)为什么Nacos需要一致性协议
首先Nacos需要实现在其内部存储数据,从而可以尽可能减少用户部署以及运维成本,只需一个程序包就可以快速以单机模式或集群模式启动Nacos。
单机模式下的内部存储很容易实现,使用简单的内嵌关系型数据库即可。集群模式下的内部存储就要考虑各节点间的数据一致性以及数据同步。而要解决这个问题,就不得不引入共识算法,通过共识算法来保障各个节点间的数据一致性。
(2)为什么Nacos会同时运行CP协议以及AP协议
这要从Nacos的应用场景出发:由于Nacos是一个集服务注册发现以及配置管理于一体的组件,因此要将集群模式下各节点间的数据一致性保障问题拆分成两方面来看。
一.从服务注册发现来看
服务注册发现中心,在当前微服务体系下,是十分重要的组件。服务间为了感知对方服务当前可正常提供服务的实例信息,需要从服务注册发现中心进行获取。
因此对于服务注册发现中心组件的可用性,提出了很高的要求,需要在任何场景下,尽最大可能保证服务注册发现能力可以对外提供服务。
同时Nacos服务注册发现,实现了基于心跳自动完成服务数据补偿的机制。如果节点出现了数据丢失,节点可以通过该机制快速补偿丢失的数据。
因此为了满足服务注册发现中心的可用性,强一致性的共识算法就不合适。因为强一致性共识算法能否对外提供服务是有要求的。如果当前集群可用的节点数没有过半的话,整个算法直接"罢工"。
而最终一致共识算法会更多保障服务的可用性,并且能保证在一定的时间内各节点间的数据能达成一致。
上述都是针对Nacos服务注册发现中的非持久化服务实例而言,即需要客户端上报心跳进行服务实例续约。即针对此类型的服务数据,选择最终一致共识算法来保障数据的一致性。
而对于Nacos服务发现注册中的持久化服务实例,因为所有数据都是直接调用Nacos服务端时进行创建的,因此需要由Nacos保障数据在各节点间的强一致性。故而针对此类型的服务数据,选择强一致性共识算法来保障数据的一致性。
二.从配置管理来看
由于配置数据是直接在Nacos服务端进行创建并进行管理的,因此必须保证大部分节点都保存了此配置数据才能认为配置被成功保存了。
否则就会丢失配置的变更,如果出现这种情况,问题是很严重的。如果发布重要配置变更时,出现丢失变更的情况,则可能会引起严重故障。
因此对于配置数据的管理,必须要求集群中大部分的节点是强一致的,这里只能使用强一致性共识算法。
(3)为什么Nacos选择Raft以及Distro协议
对于强一致性共识算法,当前工业生产中,最多使用的就是Raft协议。Raft协议更容易让人理解,并且有很多成熟的工业算法实现。比如JRaft、BRaft、Zookeeper的ZAB、Consul的Raft、Apache的Ratis。
因为Nacos是Java技术栈,所以只能在JRaft、ZAB、Apache Ratis中选择。首先,ZAB和Zookeeper强绑定。然后,JRaft支持多RaftGroup,而且可以让Nacos支持多数据分片。所以Nacos最终选择了JRaft这种强一致性协议。
Distro协议是阿里自研的一个最终一致性协议。最终一致性的协议有很多,比如Gossip、Eureka内的数据同步算法。而Distro协议是集Gossip以及Eureka协议的优点并加以优化出来的。
原生的Gossip协议:由于随机选取节点发送消息,所以存在重复发送消息给同一节点的问题。从而增加网络传输压力,也给消息节点带来额外处理负载。
Distro协议引入了权威Server的概念:也就是每个节点负责一部分数据以及将自己的数据同步给其他节点,从而有效地避免了消息冗余的问题。
(4)Nacos早期的一致性协议
先来看早期的Naocs版本的架构:
在Nacos早期的架构中,服务注册发现和配置管理的一致性协议是分开的,并没有下沉到Nacos的内核模块作为通用的能力来进行演进。
服务注册发现的一致性协议和服务注册发现的业务逻辑强耦合在一起,且充斥着服务注册发现的一些概念,使服务注册发现的整体逻辑难以维护。
由于服务注册发现的业务逻辑耦合了一致性协议层的数据状态,所以难以做到计算存储彻底分离,以及影响了计算层的无限水平扩容能力。
因此为了解决这个问题,必然要对Nacos的一致性协议进行抽象以及下沉,使一致性协议成为内核模块的功能,让服务注册发现模块只充当计算角色,同时为配置模块去外部数据库存储打下架构基础。
(5)Nacos当前的一致性协议
接着看当前的Nacos版本的架构:
在新的Nacos架构中,已完成了将一致性协议从原先的服务注册发现模块下沉到了内核模块中,并且提供了统一的抽象接口。使上层的服务注册发现模块和配置管理模块,不再需要耦合任何一致性语义。当解耦抽象分层后,每个模块都能快速演进,并且性能和可用性都实现大幅提升。
(6)Nacos如何做到一致性协议下沉的
Nacos如何做到将AP、CP协议下沉到内核模块中?
一.对一致性协议进行抽象
所谓一致性协议,就是用来保证数据的读写是一致的。一致性协议最基础的两个方法,就是写入数据的方法和读取数据的方法。
public interface ConsistencyProtocol<T extends Config, P extends RequestProcessor> extends CommandOperations {
...
//Obtain data according to the request.
//@param request request
//@return data {@link Response}
//@throws Exception {@link Exception}
Response getData(ReadRequest request) throws Exception;
//Data operation, returning submission results synchronously.
//@param request {@link com.alibaba.nacos.consistency.entity.WriteRequest}
//@return submit operation result {@link Response}
//@throws Exception {@link Exception}
Response write(WriteRequest request) throws Exception;
...
}
任何使用一致性协议的组件,都只需要使用getData()方法和write()方法即可。同时,一致性协议已经被抽象在了consistency的包中。Nacos对于AP、CP的一致性协议接口使用抽象都在里面。并且在实现具体的一致性协议时,采用了插件可插拔的形式,进一步将一致性协议的逻辑,从服务注册发现、配置管理模块中解耦。
public class ProtocolManager extends MemberChangeListener implements DisposableBean {
...
private void initAPProtocol() {
ApplicationUtils.getBeanIfExist(APProtocol.class, protocol -> {
Class configType = ClassUtils.resolveGenericType(protocol.getClass());
Config config = (Config) ApplicationUtils.getBean(configType);
injectMembers4AP(config);
protocol.init((config));
ProtocolManager.this.apProtocol = protocol;
});
}
private void initCPProtocol() {
ApplicationUtils.getBeanIfExist(CPProtocol.class, protocol -> {
Class configType = ClassUtils.resolveGenericType(protocol.getClass());
Config config = (Config) ApplicationUtils.getBean(configType);
injectMembers4CP(config);
protocol.init((config));
ProtocolManager.this.cpProtocol = protocol;
});
}
...
}
其实,仅对一致性协议进行抽象是不够的。因为此时服务注册发现、配置管理模块,还要依赖一致性协议的接口。服务注册发现、配置管理模块依然要在显式处理一致性协议的读写逻辑,以及要自己实现一个对接一致性协议的存储,这并不优雅。
服务发现以及配置模块,应该专注于数据的使用以及计算,而不是专注于数据如何存储、如何保障数据一致性。数据存储以及多节点一致的问题则应该交由存储层来保证。
为了降低一致性协议出现在服务注册发现、配置管理这两个模块中的频次,以及尽可能让一致性协议只在内核模块中感知,Nacos做了另一个处理——对数据存储进行抽象。
二.对数据存储进行抽象
一致性协议,就是用来保证数据一致的。
如果利用一致性协议实现存储接口,那么服务注册发现模块和配置管理模块,就由原来的依赖一致性协议接口转变为依赖存储接口。
而存储接口的具体实现,就比一致性协议要丰富得多了。并且这两个模块也无需为直接依赖一致性协议而承担多余的编码工作,比如快照处理、状态机实现、数据同步等编码工作,从而使得这两个模块可以更加专注自己的核心逻辑。
对于数据存储的抽象,以服务注册发现模块为例:
public interface KvStorage {
enum KvType {
//Local file storage.
File,
//Local memory storage.
Memory,
//LSMTree storage.
LSMTree,
AP,
CP,
}
//获取一个数据
byte[] get(byte[] key) throws KvStorageException;
//存入一个数据
void put(byte[] key, byte[] value) throws KvStorageException;
//删除一个数据
void delete(byte[] key) throws KvStorageException;
...
}
由于服务注册发现模块的存储,更多是根据key去执行点查的操作,因此Key-Value类型的存储接口最适合不过。而Key-Value的存储接口定义好后,其实就是这个KVStore的具体实现了。
可以直接将KVStore的实现对接Redis,也可以直接对接DB。或者直接根据Nacos内核模块的一致性协议,在此基础上实现一个内存或者持久化的分布式强(弱)一致性KV。
通过功能边界将Nacos进程进一步分离为计算逻辑层和存储逻辑层,计算层和存储层之间的交互仅通过一层薄薄的数据操作胶水代码来处理。这样就在单个Nacos进程里实现了计算和存储逻辑的彻底分离。
同时,基于存储层的插件化设计:如果中小公司有运维成本要求,可以直接使用Nacos自带的内嵌分布式存储组件来部署一套Nacos集群。
如果中大型公司的服务实例数据、配置数据的量级很大,并且本身有一套比较好的Paas层服务,则完全可以重写已有的存储组件,从而实现Nacos计算层与存储层的彻底分离。