服务注册中心,是一个给服务提供者注册服务、给服务消费者获取服务信息的地方,一般还提供服务列表查询、心跳检测等功能。注册中心为保证可用性一般集群部署。
注册中心组件我们可选的组件有Eureka、ZK、Nacos,ZK支持CP,Eureka支持AP,Nacos可支持AP也可支持CP。下面分别阐述下。
一、Eureka
Eureka分服务端与客户端,服务端为注册中心,也就是图中的eureka server,客户端指的是微服务系统中各个微服务,也就是图中的application server和application client,可以是服务提供都也可以是服务消费者,客户端完成向服务端注册与服务发现。服务端的主要工作有:
- 提供服务注册。提供一个统一存储服务的地方,客户端(服务提供者)将服务注册后服务端提供管理
- 提供注册表。为客户端(服务消费者)提供服务列表的查询,也就是服务发现功能。客户端获取服务列表后一般会在本地进行缓存,以减少与注册中心交互
- 服务剔除。如果客户端在一定时间内未上报心跳,服务端会剔除此服务实例
- 自我保护机制。一段时间内如果客户端可用率低于一定比例则会进入自我保护阶段,防止Eureka客户端本身是可以正常访问的,但是由于网路通信故障等原因,造成Eureka服务端失去于客户端的连接,从而形成的不可用
客户端的功能这里只是简单罗列一下:服务注册、自动刷新缓存获取最新服务列表、服务续约上报心跳、远程调用、服务下线。
注册与发现的工作流程:
- server启动后,client启动后将服务注册到server
- client默认每30s向server发起心跳
- server若 90s没收到client的心跳请求,则统计15分钟内是否有超过85%的比例,如果有进入自我保护状态;如果没有则剔除该client
- client定时调用server接口获取服务列表更新本地缓存
- client远程调用时,先从本地缓存找,如果找到则直接发起调用,如果没有则先向server进行查询后再发起调用
- 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<String, Map<String, Lease<InstanceInfo>>> registry= new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
- 第一层的 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 量获取服务列表信息,并保持在本地缓存中。
Eureka如何做到高可用呢?主要是通过集群部署、心跳机制、自我保护机制、服务备份和客户端缓存等实现的,具体可参考:怎么在Eureka实现高可用性
二、ZK
ZK作为注册中心原理主发依赖于其自身的文件,ZK 的文件结构类似于 Linux 系统的树状结构,注册服务时,即在 ZK 中创建一个唯一的 znode 节点来保存服务的 IP、端口、服务名等信息;发现服务时,遍历树状结构文件找到具体的 znode 节点或者服务相关信息进行远程调用。
注册与发现的工作流程
- ZK 已经启动,服务提供者启动时把服务注册到 ZK 注册中心;
- ZK 注册中心和服务提供者之间建立一个 Socket 长连接,ZK 注册中心定时向每个服务提供者发数据包,如果服务提供者没响应,则剔除该服务提供者实例,把更新后的服务列表发送给所有服务消费者(即通知);
- 服务消费者启动时到 ZK 注册中心获取一份服务列表缓存到本地供以后使用;
- 服务消费者远程调用服务时,先从本地缓存找,如果找到则直接发起服务调用,如果没有则到 ZK 注册中心获取服务列表缓存到本地后再发起服务调用;
- 当其中一个服务提供者宕机或正常关闭时,ZK 注册中心会把该节点剔除,并通知所有服务消费者更新本地缓存
- 当这个服务提供者正常启动后,ZK 注册中心也能感知到,并通知所有服务消费者更新本地缓存。
ZK与Eureka的区别
- 根据 CAP 定律,ZooKeeper 支持 CP,Eureka 支持 AP。因为 ZK 集群中如果有节点宕机则需要选举 leader,选举过程需要 30 至 120 秒,选举过程时集群不可用,牺牲时间来保证数据一致性,因此支持 CP;而 Eureka 每个节点的数据都一致,没有主从节点之分,不需选举,如果其中一个节点宕机则马上切换到另外一个健康的节点上,保证可用性,因此支持 AP。
- 微服务架构当中,可用性比一致性更重要些,Eureka 比 ZooKeeper 更合适,而 ZooKeeper 更适合做分布式协调服务,比如:hadoop 集群。
这里简单提一下京东自研的RPC框架JSF,其注册中心为NameServer,与Eureka各节点一样,它们之间也是平等的、各节点存着全量数据,数据的同步采用的是消息总线的方式,显而易见,NameServer属于AP原则,通过消息总线来保证最终一致性。
三、Nacos
Nacos既能作为注册中心也可以作为配置中心,下面是作为注册中心的流程:
- Nacos启动后,服务提供者启动时将服务注册到Nacos
- 服务提供者定时发送http请求,上报心跳
- Nacos长时间没收到服务提供者的心跳,则剔除该实例
- 服务消费者发现服务支持两种方式,一种是主动请求注册中心获取服务列表(图中左下角不推荐),一种是订阅注册中心的服务并提交一个 Listener,如果注册中心的服务有变更,由 Listener 来通知服务消费者更新本地服务列表(图中右下角)
Nacos里的CP与AP
先看一下Nacos的架构图:
我们可以看到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算法
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。