服务注册中心Eureka vs Zookeeper vs Consul

前言 

  在现在云计算和大数据快速发展的今天,业务快速发展和变化。我们以前的单一应用难以应对这种快速的变化, 因此我们需要将以前单一的大应用不断进行差分,分成若干微小的应用或者服务,这就是微服务的思想。但是当我们对服务进行拆分之后,我们将又面临新的问题。服务之间该如何调用?直接调用?当服务很少只有几个的时候你可能感觉直接调用是最高效和便捷的,但是当服务多达上千个呢?这时我们又该怎么来管理我们的调用关系呢?

 服务间直接调用

服务间直接调用

服务注册中心解决的问题

  服务注册中心主要解决两个关键问题:服务注册和服务发现。

  • 服务注册:服务实例将自身服务信息注册到注册中心。这部分服务信息包括服务所在主机IP和提供服务的Port,以及暴露服务自身状态以及访问协议等信息。
  • 服务发现:服务实例请求注册中心获取所依赖服务信息。服务实例通过注册中心,获取到注册到其中的服务实例的信息,通过这些信息去请求它们提供的服务。

  除了这两个核心功能之外,一般服务注册中心还需要监控服务实例的运行状态,负载均衡等问题。

  • 监控:服务实例一直处于动态的变化中,因此我们需要监控服务实例的健康状况,从注册中心剔除无用的服务。一般实现心跳连接等。
  • 负载均衡:在一个服务有多个实例的情况下,我们需要根据负载均衡策略正确处理请求。

服务注册中心模式

CAP原则

  这里不得不提一下,我们知道分布式里一个重要的理论,那就是CAP原则。指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性),不能同时成立。

  • 一致性:它要求在同一时刻点,分布式系统中的所有数据备份都处于同一状态。
  • 可用性:在系统集群的一部分节点宕机后,系统依然能够响应用户的请求。
  • 分区容错性:在网络区间通信出现失败,系统能够容忍。

CAP原则

  一般来讲,基于网络的不稳定性,分布容错是不可避免的,所以我们默认CAP中的P总是成立的。一致性的强制数据统一要求,必然会导致在更新数据时部分节点处于被锁定状态,此时不可对外提供服务,影响了服务的可用性,反之亦然。因此一致性和可用性不能同时满足。在注册中心的发展上面,一直有两个分支:一个就是 CP 系统,追求数据的强一致性。还有一个是 AP 系统,追求高可用与最终一致。我们接下来介绍的服务注册和发现组件中,Eureka满足了其中的AP,Consul和Zookeeper满足了其中的CP。

Eureka

  Eureka是在Java语言上,基于Restful Api开发的服务注册与发现组件,由Netflix开源。遗憾的是,目前Eureka仅开源到1.X版本,2.X版本已经停止开发。Eureka由两个组件组成:Eureka服务器和Eureka客户端。Eureka服务器用作服务注册服务器。Eureka客户端是一个java客户端,用来简化与服务器的交互、作为轮询负载均衡器,并提供服务的故障切换支持。Netflix在其生产环境中使用的是另外的客户端,它提供基于流量、资源利用率以及出错状态的加权负载均衡。Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现。Eureka 采用了 C-S 的设计架构。Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server,并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。Spring Cloud 的一些其他模块(比如Zuul)就可以通过 Eureka Server 来发现系统中的其他微服务,并执行相关的逻辑。

  上图来自Eureka官方的架构图,大致描述了Eureka集群的工作过程。图中包含的组件非常多,可能比较难以理解,我们用通俗易懂的语言解释一下: 

  • Application Service: 作为Eureka Client,扮演了服务的提供者,提供业务服务,向Eureka Server注册和更新自己的信息,同时能从Eureka Server的注册表中获取到其他服务的信息。
  • Eureka Server:扮演服务注册中心的角色,提供服务注册和发现的功能,每个Eureka CientEureka Server注册自己的信息,也可以通过Eureka Server获取到其他服务的信息达到发现和调用其他服务的目的。
  • Application Client:作为Eureka Client,扮演了服务消费者,通过Eureka Server获取到注册到上面的其他服务的信息,从而根据信息找到所需的服务发起远程调用。
  • Replicate Eureka Server中的注册表信息的同步拷贝,保持不同的Eureka Server集群中的注册表中的服务实例信息的一致性。提供了数据的最终一致性。
  • Make Remote Call: 服务之间的远程调用。
  • Register: 注册服务实例,Client端向Server端注册自身的元数据以进行服务发现。
  • Renew:续约,通过发送心跳到Server维持和更新注册表中的服务实例元数据的有效性(默认周期为30秒)。当在一定时长内Server没有收到Client的心跳信息(默认90秒),将默认服务下线,将服务实例的信息从注册表中删除。
  • Cancel:服务下线,Client在关闭时主动向Server注销服务实例元数据,这时Client的的服务实例数据将从Server的注册表中删除。

 

最后我们需要注意的Eureka的缓存机制。Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者。Ereka Client客户端默认每30秒发送一次请求来更新缓存的注册信息。如果你并不是在Spring Cloud环境下使用这些组件(Eureka, Ribbon),你的服务启动后并不会马上向Eureka注册,而是需要等到第一次发送心跳请求时才会注册。心跳请求的发送间隔也是30s。(Spring Cloud对此做了修改,服务启动后会马上注册)

Zookeeper

ZooKeeper 是一个开源的分布式协调服务,由雅虎创建,是 Google Chubby 的开源实现。 分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协 调/通知、集群管理、Master 选举、配置维护,名字服务、分布式同步、分布式锁和分布式队列 等功能。为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。客户端在使用 ZooKeeper 时,需要知道集群机器列表,通过与集群中的某一台机器建立 TCP 长连接来使用服务。客户端使用这个 TCP 链接来发送请求、获取结果、获取监听事件以及发送心跳包。如果这个连接异常断开了,客户端可以连接到另外的机器上。

 

 

  上图是ZooKeeper 官方提供的架构图,图中每一个 Server 代表一个安装 ZooKeeper 服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 Zab 协议(Zookeeper AtomicBroadcast)来保持数据的一致性。对于来自客户端的每个更新请求,ZooKeeper 都会分配一个全局唯一的递增编号。这个编号反应了所有事务操作的先后顺序,应用程序可以使用 ZooKeeper 这个特性来实现更高层次的同步原语。这个编号也叫做时间戳—zxid(ZooKeeper Transaction Id)。

  最典型集群模式:Master/Slave 模式(主备模式)。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。但是,在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了Leader、Follower 和 Observer 三种角色。如下图所示:

 

  • ZooKeeper 集群中的所有机器通过一个 Leader 选举过程来选定一台称为 “Leader” 的机器。
  • Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和  Observer 都只能提供读服务
  • Server:Server中存在两种类型:Follower和Observer。其中Follower接受客户端的请求并返回结果(事务请求将转发给Leader处理),并在选举过程中参与投票;Observer与Follower功能一致,唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。
  • Client:请求发起方,Server和Client之间可以通过长连接的方式进行交互。如发起注册或者请求集群信息等。
  • 集群间通过 Zab 协议(Zookeeper Atomic Broadcast)来保持数据的一致性。 

Zab协议

  ZAB(ZooKeeper Atomic Broadcast 原子广播)协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。ZAB 协议包括两种基本的模式,分别是崩溃恢复和消息广播。

  当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致。当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进人消息广播模式了。当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播。那么新加入的服务器就会自觉地进人数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。正如上文介绍中所说的,ZooKeeper 设计成只允许唯一的一个 Leader 服务器来进行事务请求的处理。Leader 服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播协议。而如果集群中的其他机器接收到客户端的事务请求,那么这些非 Leader 服务器会首先将这个事务请求转发给 Leader 服务器。

consul

  Consul是HashiCorp公司推出的开源工具,基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件,采用Raft算法保证服务的一致性,且支持健康检查。因为通过Golang实现,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与Docker等轻量级容器可无缝配合。Consul采用主从模式的设计,使得集群的数量可以大规模扩展,集群间通过RPC的方式调用(HTTP和DNS)。它的结构图如下所示:

  • Client:作为一个代理(非微服务实例),它将转发所有的RPC请求到Server中。作为相对无状态的服务,它不持有任何注册信息。
  • Server:作为一个具备扩展功能的代理,它将响应RPC查询、参与Raft选举、维护集群状态和转发查询给Leader等。
  • Leader-Server:一个数据中心的所有Server都作为Raft节点集合的一部分。其中Leader将负责所有的查询和事务(如服务注册),同时这些事务也会被复制到所有其他的节点。
  • Data Center:数据中心作为一个私有的,低延迟和高带宽的一个网络环境。每个数据中心会存在Consul集群,一般建议Server是3-5台(考虑到Raft算法在可用性和性能上取舍),而Leader只能唯一,Client的数量没有限制,可以轻松扩展。Consul 通过 WAN 的 Gossip 协议,完成跨数据中心的同步;而且其他的产品则需要额外的开发工作来实现。

Raft算法

  不同于Paxos算法直接从分布式一致性问题出发推导出来,Raft算法则是从多副本状态机的角度提出,用于管理多副本状态机的日志复制。Raft实现了和Paxos相同的功能,它将一致性分解为多个子问题:Leader选举(Leader election)、日志同步(Log replication)、安全性(Safety)、日志压缩(Log compaction)、成员变更(Membership change)等。同时,Raft算法使用了更强的假设来减少了需要考虑的状态,使之变的易于理解和实现。

 

  Raft算法将Server分为三种类型:Leader、Follower和Candidate。Leader处理所有的查询和事务,并向Follower同步事务。Follower会将所有的RPC查询和事务转发给Leader处理,它仅从Leader接受事务的同步。数据的一致性以Leader中的数据为准实现。在节点初始启动时,节点的Raft状态机将处于Follower状态等待来来自Leader节点的心跳。如果在一定时间周期内没有收到Leader节点的心跳,节点将发起选举。Follower节点选举时会将自己的状态切换为Candidate,然后向集群中其它Follower节点发送请求,询问其是否选举自己成为Leader。当收到来自集群中过半数节点的接受投票后,节点即成为Leader,开始接收Client的事务处理和查询并向其它的Follower节点同步事务。Leader节点会定时向Follower发送心跳来保持其地位。

Gossip协议

  Gossip protocol 也叫 Epidemic Protocol (流行病协议),实际上它还有很多别名,比如:“流言算法”、“疫情传播算法”等。Gossip 过程是由种子节点发起,当一个种子节点有状态需要更新到网络中的其他节点时,它会随机的选择周围几个节点散播消息,收到消息的节点也会重复该过程,直至最终网络中所有的节点都收到了消息。这个过程可能需要一定的时间,由于不能保证某个时刻所有节点都收到消息,但是理论上最终所有节点都会收到消息,因此它是一个最终一致性协议。这个协议的作用就像其名字表示的意思一样,非常容易理解,它的方式其实在我们日常生活中也很常见,比如电脑病毒的传播,森林大火,细胞扩散等等。

  为了表述清楚,我们先做一些前提设定

  1.   Gossip 是周期性的散播消息,把周期限定为 1 秒
  2.   被感染节点随机选择 k 个邻接节点(fan-out)散播消息,这里把 fan-out 设置为 3,每次最多往 3 个节点散播。
  3.   每次散播消息都选择尚未发送过的节点进行散播
  4.   收到消息的节点不再往发送节点散播,比如 A -> B,那么 B 进行散播的时候,不再发给 A。

  这里一共有 16 个节点,节点 1 为初始被感染节点,通过 Gossip 过程,最终所有节点都被感染。

  Gossip 的特点(优势)

  1)扩展性

  网络可以允许节点的任意增加和减少,新增加的节点的状态最终会与其他节点一致。

  2)容错

  网络中任何节点的宕机和重启都不会影响 Gossip 消息的传播,Gossip 协议具有天然的分布式系统容错特性。

  3)去中心化

  Gossip 协议不要求任何中心节点,所有节点都可以是对等的,任何一个节点无需知道整个网络状况,只要网络是连通的,任意一个节点就可以把消息散播到全网。

  4)一致性收敛

  Gossip 协议中的消息会以一传十、十传百一样的指数级速度在网络中快速传播,因此系统状态的不一致可以在很快的时间内收敛到一致。消息传播速度达到了 logN。

  5)简单

  Gossip 协议的过程极其简单,实现起来几乎没有太多复杂性。

  Gossip 的缺陷

  分布式网络中,没有一种完美的解决方案,Gossip 协议跟其他协议一样,也有一些不可避免的缺陷,主要是两个:

  1)消息的延迟

  由于 Gossip 协议中,节点只会随机向少数几个节点发送消息,消息最终是通过多个轮次的散播而到达全网的,因此使用 Gossip 协议会造成不可避免的消息延迟。不适合用在对实时性要求较高的场景下。

  2)消息冗余

  Gossip 协议规定,节点会定期随机选择周围节点发送消息,而收到消息的节点也会重复该步骤,因此就不可避免的存在消息重复发送给同一节点的情况,造成了消息的冗余,同时也增加了收到消息的节点的处理压力。而且,由于是定期发送,因此,即使收到了消息的节点还会反复收到重复消息,加重了消息的冗余。

  Gossip协议是为了解决分布式环境下监控和事件通知的瓶颈。Gossip协议中的每个Agent会利用Gossip协议互相检查在线状态,分担了服务器节点的心跳压力,通过Gossip广播的方式发送消息。所有的Agent都运行着Gossip协议。服务器节点和普通Agent都会加入这个Gossip集群,收发Gossip消息。每隔一段时间,每个节点都会随机选择几个节点发送Gossip消息,其他节点会再次随机选择其他几个节点接力发送消息。这样一段时间过后,整个集群都能收到这条消息。基于Raft算法,Consul提供强一致性的注册中心服务,但是由于Leader节点承担了所有的处理工作,势必加大了注册和发现的代价,降低了服务的可用性。通过Gossip协议,Consul可以很好地监控Consul集群的运行,同时可以方便通知各类事件,如Leader选择发生、Server地址变更等。

总结

  下面是这三个服务注册中心的特性对比, 没有最好的服务中心, 只有最合适的,我们可以根据项目的实际情况来进行选择。

 

posted @ 2019-01-14 21:57  Dan_Go  阅读(6020)  评论(0编辑  收藏  举报