注册中心

服务注册中心,是一个给服务提供者注册服务、给服务消费者获取服务信息的地方,一般还提供服务列表查询、心跳检测等功能。注册中心为保证可用性一般集群部署。
注册中心组件我们可选的组件有Eureka、ZK、Nacos,ZK支持CP,Eureka支持AP,Nacos可支持AP也可支持CP。下面分别阐述下。

一、Eureka

image.png

Eureka分服务端与客户端,服务端为注册中心,而客户端完成向服务端注册与服务发现。服务端的主要工作有:

  • 提供服务注册。提供一个统一存储服务的地方,客户端(服务提供者)将服务注册后服务端提供管理
  • 提供注册表。为客户端(服务消费者)提供服务列表的查询,也就是服务发现功能。客户端获取服务列表后一般会在本地进行缓存,以减少与注册中心交互
  • 服务剔除。如果客户端在一定时间内未上报心跳,服务端会剔除此服务实例
  • 自我保护机制。一段时间内如果客户端可用率低于一定比例则会进入自我保护阶段,防止Eureka客户端本身是可以正常访问的,但是由于网路通信故障等原因,造成Eureka服务端失去于客户端的连接,从而形成的不可用

客户端的功能这里只是简单罗列一下:服务注册、自动刷新缓存获取最新服务列表、服务续约上报心跳、远程调用、服务下线。

注册与发现的工作流程:

  1. server启动后,client启动后将服务注册到server
  2. client默认每30s向server发起心跳
  3. server若 90s没收到client的心跳请求,则统计15分钟内是否有超过85%的比例,如果有进入自我保护状态;如果没有则剔除该client
  4. client定时调用server接口获取服务列表更新本地缓存
  5. client远程调用时,先从本地缓存找,如果找到则直接发起调用,如果没有则先向server进行查询后再发起调用
  6. client关闭时会发http请求到server,server接受请求后将该实例剔除

Eureka是CAP里的AP
从CAP理论看,Eureka是一个AP系统,其优先保证可用性(A)和分区容错性,不保证强一致性,但能做到最终一致性。

  • 只要集群中任意一个实例不出现问题,Eureka服务就是可用的;即Eureka Client 在向某个 Eureka Server 注册时,如果发现连接失败,则会自动切换至其它节点;
  • Eureka集群中没有主从的概念,各个节点都是平等的,节点间采用Replicate异步的方式来同步数据;

由于Eureka并不强调一致性而侧重可用性,在设计上为提升性能采用了多级缓存的方案。这种设计和mysql的读写分离及JDK里的CopyOnWriteArrayList有点类似,目的是为了使操作不阻塞读操作。

Eureka数据存储机制

Eureka没有采用数据库这类存储介质,它的数据层分数据存储层缓存层。数据存储层记录注册到 Eureka Server 上的服务信息,缓存层是经过包装后的数据,可以直接在 Eureka Client 调用时返回。

存储层
我们先来看看数据存储层的数据结构,它底层是一个双层HashMap:

private final ConcurrentHashMap>> registry= new ConcurrentHashMap>>();
  • 第一层的 ConcurrentHashMap 的 key=spring.application.name 也就是客户端实例注册的应用名;value 为嵌套的 ConcurrentHashMap。
  • 第二层嵌套的 ConcurrentHashMap 的 key=instanceId 也就是服务的唯一实例 ID,value 为 Lease 对象,Lease 对象存储着这个实例的所有注册信息,包括 ip 、端口、属性等。

缓存层
接下来我们再来看看缓存层。

Eureka Server 为了提供响应效率,提供了两层的缓存结构,将 Eureka Client 所需要的注册信息,直接存储在缓存结构中。

  • 第一层缓存:readOnlyCacheMap,本质上是 ConcurrentHashMap,依赖定时从 readWriteCacheMap 同步数据,默认时间为 30 秒。

readOnlyCacheMap : 是一个 CurrentHashMap 只读缓存,这个主要是为了供客户端获取注册信息时使用,其缓存更新,依赖于定时器的更新,通过和 readWriteCacheMap 的值做对比,如果数据不一致,则以 readWriteCacheMap 的数据为准。

  • 第二层缓存:readWriteCacheMap,本质上是 Guava 缓存。

readWriteCacheMap:readWriteCacheMap 的数据主要同步于存储层。当获取缓存时判断缓存中是否没有数据,如果不存在此数据,则通过 CacheLoader 的 load 方法去加载,加载成功之后将数据放入缓存,同时返回数据。
readWriteCacheMap 缓存过期时间,默认为 180 秒,当服务下线、过期、注册、状态变更,都会来清除此缓存中的数据。

存储类型 数据结构 概述
readOnlyCacheMap 一级缓存 ConcurrentHashMap 周期更新,默认每30s从二级缓存readWriteCacheMap中同步数据更新;Eureka Client默认从这里获取服务注册信息,可配为直接从readWriteCacheMap获取
readWriteCacheMap 二级缓存 Guava Cache 服务有变动时实时更新,缓存时间180秒,如果不存在此数据,则通过 CacheLoader 的 load 方法去registry层加载
registry 存储层 双层HashMap 服务有变动时实时更新,又名注册表,UI界面从这里获取服务注册信息

客户端查询流程

客户端查询服务端数据的流程是怎样的呢,这里暂时不考虑客户端自身的缓存。

Eureka Client 获取全量或者增量的数据时,会先从一级缓存中获取;如果一级缓存中不存在,再从二级缓存中获取;如果二级缓存也不存在,这时候先将存储层的数据同步到缓存中,再从缓存中获取。

通过 Eureka Server 的二层缓存机制,可以非常有效地提升 Eureka Server 的响应时间,通过数据存储层和缓存层的数据切割,根据使用场景来提供不同的数据支持。

客户端缓存

客户端缓存只是简单提一下:
Eureka Client 也同样存在着缓存机制,Eureka Client 启动时会全量拉取服务列表,启动后每隔 30 秒从 Eureka Server 量获取服务列表信息,并保持在本地缓存中。

二、ZK

image.png

ZK作为注册中心原理主发依赖于其自身的文件,ZK 的文件结构类似于 Linux 系统的树状结构,注册服务时,即在 ZK 中创建一个唯一的 znode 节点来保存服务的 IP、端口、服务名等信息;发现服务时,遍历树状结构文件找到具体的 znode 节点或者服务相关信息进行远程调用。

注册与发现的工作流程

  1. ZK 已经启动,服务提供者启动时把服务注册到 ZK 注册中心;
  2. ZK 注册中心和服务提供者之间建立一个 Socket 长连接,ZK 注册中心定时向每个服务提供者发数据包,如果服务提供者没响应,则剔除该服务提供者实例,把更新后的服务列表发送给所有服务消费者(即通知);
  3. 服务消费者启动时到 ZK 注册中心获取一份服务列表缓存到本地供以后使用;
  4. 服务消费者远程调用服务时,先从本地缓存找,如果找到则直接发起服务调用,如果没有则到 ZK 注册中心获取服务列表缓存到本地后再发起服务调用;
  5. 当其中一个服务提供者宕机或正常关闭时,ZK 注册中心会把该节点剔除,并通知所有服务消费者更新本地缓存
  6. 当这个服务提供者正常启动后,ZK 注册中心也能感知到,并通知所有服务消费者更新本地缓存。

ZK与Eureka的区别

  1. 根据 CAP 定律,ZooKeeper 支持 CP,Eureka 支持 AP。因为 ZK 集群中如果有节点宕机则需要选举 leader,选举过程需要 30 至 120 秒,选举过程时集群不可用,牺牲时间来保证数据一致性,因此支持 CP;而 Eureka 每个节点的数据都一致,没有主从节点之分,不需选举,如果其中一个节点宕机则马上切换到另外一个健康的节点上,保证可用性,因此支持 AP。
  2. 微服务架构当中,可用性比一致性更重要些,Eureka 比 ZooKeeper 更合适,而 ZooKeeper 更适合做分布式协调服务,比如:hadoop 集群。

这里简单提一下京东自研的RPC框架JSF,其注册中心为NameServer,与Eureka各节点一样,它们之间也是平等的、各节点存着全量数据,数据的同步采用的是消息总线的方式,显而易见,NameServer属于AP原则,通过消息总线来保证最终一致性。

三、Nacos

image.png

Nacos既能作为注册中心也可以作为配置中心,下面是作为注册中心的流程:

  1. Nacos启动后,服务提供者启动时将服务注册到Nacos
  2. 服务提供者定时发送http请求,上报心跳
  3. Nacos长时间没收到服务提供者的心跳,则剔除该实例
  4. 服务消费者发现服务支持两种方式,一种是主动请求注册中心获取服务列表(图中左下角不推荐),一种是订阅注册中心的服务并提交一个 Listener,如果注册中心的服务有变更,由 Listener 来通知服务消费者更新本地服务列表(图中右下角)

Nacos里的CP与AP

先看一下Nacos的架构图:

image.png

我们可以看到Nacos是集成了Raft与Distro这两种一致性协议的,我们先从Distro入手,看下Nacos的设计机制:

  • 平等机制:nacos每个节点是平等的,都可以处理写请求,同时会将数据同步到其它节点。
  • 路由转发机制:客户端发送的写请求,如果属于自己则处理,否则路由转发给其它节点
  • 本地计机制:每个节点独立处理读请求,及时从本地发出响应,因为每个节点都存有全量数据
  • 异步复制机制:节点间通过1s的延迟任务,将数据同步给其它节点
  • 健康检查机制:每个节点只存部分数据,定期检查客户端状态保持数据一致性
  • 新节点同步机制:服务实例注册到nacos后,通过UDP的方式推送到所有服务实例,让其它服务实例感知到服务列表的变化

Nacos哪些地方用到了AP与CP呢?

  • 对于临时服务实例,采用AP来保证注册中心的可用性,Distro协议
  • 对于持久化服务实例,采用CP保证各个节点的强一致性,JRaft协议(Nacos对Raft的改造)
  • 对于配置中心,无Database 作为存储的情况下,Nacos 节点之间的内存数据为了保持一致,采用 CP
  • 对于配置中心,有 Database 作为存储的情况下,Nacos 通过持久化后通知其他节点到数据库拉取数据来保证数据一致性,另外采用读写分离架构来保证高可用,这里应该是AP

Nacos、ZK、Eureka区别

Eureka不能支撑大量服务实例,因为它的每个节点之间会产生大量心跳检查导致并发性能降低;ZK如果出现频繁上下线通知也会导致性能下降;Nacos可以支持大量服务实例而又不丢失性能,服务数量可达到10万级别。

参考文章:
《服务注册与发现原理剖析(Eureka、Zookeeper、Nacos)》
Eureka 缓存机制详细配置
图文详述Eureka的缓存机制/三级缓存
Nacos 一致性协议:Distro协议
揭秘 Nacos 的 AP 架构 「Distro 一致性协议」
对标Eureka的AP一致性,Nacos如何实现Raft算法

你可能感兴趣的:(注册中心)