在分布式系统中,服务(或组件)之间的协调是非常重要的,构成了分布式系统的基础。分布式系统中的leader选举、分布式锁、分布式队列等,均需要通过协调服务实现。
ZooKeeper引入一致性协议ZAB,能够让多个对等的服务达成一致,从而实现数据多副本存储。ZooKeeper通过引入类似于文件系统的层级命名空间,并在此基础上提供了一套简单易用的原语能够帮助用户轻易地实现leader选举等问题。
ZooKeeper集群部署参考:CentOS7搭建Zookeeper集群
1.leader选举
在分布式系统中,常见的一种软件设计架构为master/slave,其中master负责集群管理,slave负责执行具体的任务(比如数据存储、处理数据),HDFS、HBase均采用了该架构。为了避免master出现故障导致整个集群不可用,常见的优化方式是引入多个master:active master和standby master。
引入双master需要解决如下两个难题:
1)如何选举出一个master作为active master?
2)如何发现active master出现故障,如何让standby master安全切换为active master?问题的难点在于如何避免出现脑裂,即集群中同时存在两个active master,造成数据不一致或集群出现混乱的现象。
几乎所有采用master/slave架构的分布式系统均存在以上问题,为了避免每个分布式系统单独开发这些功能造成工作冗余,构造一个可靠的协调服务势在必行。该协调服务需具备leader(master)选举和服务状态获取等基本功能。
2.负责均衡
在类似于Kafka的分布式消息队列中,生产者将数据写入分布式队列,消费者从分布式消息队列中读取数据进行处理,为了实现该功能,需要从架构上解决以下两个问题:
1)生产者和消费者如何获知最新的消息队列位置?
消息队列是分布的,通常由一组节点构成,这些节点的健康状态是动态变化的,比如某个节点因机器故障变得对外不可用,如何让生产者和消费者动态获知最新的消息队列节点位置是必须要解决的问题。
2)如何让生产者将数据均衡地写入消息队列中各个节点?
消息队列提供了一组可存储数据的节点,需让生产者及时了解各个存储节点的负责,以便智能决策将数据均衡地写入这些节点。
为了解决以上两个问题,需要引入一个可靠的分布式协调服务,具备简单的元信息存储和动态获取服务状态等基本功能。ZooKeeper将服务协调的职责从分布式系统中独立处理,以减少系统的耦合性和增强扩充性。
ZooKeeper非常类似一个分布式文件系统。
1.层级命名空间
ZooKeeper层级命名空间,整个命名方式类似于文件系统,以多叉树形式组织在一起。
每个节点被称为“znode”,主要包含以下几个属性:
1)data:每个znode拥有一个数据域,记录了用户数据,该域的数据类型为字节数组。ZooKeeper通过多副本方式保证数据的可靠性。
2)type:znode类型。
3)version:znode中数据的版本号,每次数据的更新会导致其版本加一。
4)children:znode可以包含子节点,但由于ephemeral类型的znode与session的生命周期是绑定的,因此ZooKeeper不允许ephemeral znode有子节点。
5)ACL: znode访问控制列表,用户可单独设置每个znode的可访问用户列表,以保证znode被安全访问。
2.Watcher
Watcher是ZooKeeper提供的发布/订阅机制,用户可在某个znode上注册watcher以监听它的变化,一旦对应的znode被删除或者更新(包括删除、数据域被修改、子节点发生变化等),ZooKeeper将以事件的形式将变化内容发送给监听者。需要注意的是,watcher一旦触发后便会被删除,除非用户再次注册该watcher。
3.Session
Session是ZooKeeper中的一个重要概念,是客户端与ZooKeeper服务端之间的通信通道。同一个session中的消息是有序的。Session具有容错性:如果客户端连接的ZooKeeper服务器宕机,客户端会自动连接到其他或者的服务器上。
ZooKeeper服务通常由奇数个ZooKeeper的Server构成,其中一个实例为leader角色,其他为follow角色,同时维护了层级目录结构的一个副本,并通过ZAB(ZooKeeper Atomic Broadcast)协议维持副本之间的一致性。ZooKeeper将所有数据保存到内存中,具有高吞吐率、延迟低等优点。
当leader出现故障时,ZooKeeper会通过ZAB协议发起新一轮的leader投票选举,保证集群中始终有一个可用的leader。
ZooKeeper中多个实例中的内存数据并不是强一致的,采用的ZAB协议只能保证同一时刻至少多数节点中的数据是强一致的。为了让客户端读到最新的数据,需给对应的ZooKeeper实例发送同步指令(可通过调用sync接口实现),强制其与leader同步数据。
Observer:随着ZooKeeper集群Server数目的增多,读吞吐率升高,但写延迟增加。为了保证集群写性能的情况下提升读吞吐率。ZooKeeper引入了Observer。Observer不参与投票,仅仅接收投票结果,除此之外功能与follower类似:可以接入正常的ZooKeeper集群,接收并处理客户端读请求,或将写请求进一步转发给leader处理。由于Observer不参与投票过程,因此出现故障并不会影响ZooKeeper集群的可用性。
使用Observer还是得接收Leader的同步结果,Observer有更新请求也必须转发到Leader,所有在网络延迟非常大的情况下还是会有影响的,使用Observer优势就是接收更多的请求的流量,却不会牺牲写操作的吞吐量。
Observer常见应用场景如下:
作为数据中心间的桥梁
数据中心之间的网络延迟,将一个ZooKeeper部署到两个数据中心会误报网络故障和网络分区导致ZooKeeper不稳定。如果将整个ZooKeeper集群部署到单独一个集群中,另一个集群只部署Observer,更新操作都在一个单独的数据中心来处理,并发送到其他数据中心,让其他数据中心的client消费数据。
作为消息总线
可将ZooKeeper作为一个可靠的消息总线使用,Observer作为一种天然的可插拔组件能够动态接入ZooKeeper集群,通过内置的发布订阅机制近实时获取新的消息。
使用Observer,需要在相应节点的zoo.cfg配置文件中加入以下配置:
peerType=observer
并在所有server的配置文件中增加:observer
server.4=kafka1:2888:3888:observer
基于ZooKeeper实现leader选举的基本思想是,让各个参与竞选的实例同时在ZooKeeper上创建指定的znode,比如/current/leader,谁创建成功则谁竞选成功,并将自己的信息(host、port等)写入该znode数据域,之后其他竞选者向该znode注册watcher,以便当前leader出现故障时,第一时间再次参与竞选。
基于ZooKeeper的leader选举流程如下,在HBase、YARN和HDFS系统,采用了类似的机制解决leader选举问题:
1)各实例启动后,尝试在ZooKeeper上创建ephemeral类型znode节点/current/leader,假设实例B创建成功,则将自己的信息写入该znode,并将自己的角色标注为leader,开始执行leader相关的初始化工作。
2)除B之外的实例得知创建znode失败,则向/current/leader注册watcher,并将自己角色标注为follower,开始执行follower相关的初始化工作。
3)系统正常运行,直到实例B因故障退出,此时znode节点/current/leader被ZooKeeper删除,其他follower收到节点被删除的事情,重新转入步骤1,开始新一轮的leader选举。
在分布式计算系统中,常见的做法是,用户将作业提交给系统的Master,并由Master将之分解成子任务后,调度给各个Worker执行。该方法存在一个问题:Master维护了所有作业和Worker信息,一旦Master出现故障,则整个集群不可用。为了避免Master维护过多状态,一种改进方式就是将所有信息保存到ZooKeeper上,进而让Master变得无状态,这使得leader选举过程更加容易。
ZooKeeper对分布式系统的协调,使用的是共享存储。分布式应用要和存储进行网络通信。网络通信是分布式系统并发设计的基础。
ZooKeeper存储了任务的分配、完成情况等共享信息。当主节点对某个从节点的分工信息做出改变时,相关订阅的从节点得到zookeeper的通知,取得最新的任务分配。完成工作后,把完成情况存储到zookeeper。主节点订阅了该任务的完成情况信息,将得到zookeeper的任务完成通知。
该方案的关键是借助ZooKeeper实现一个分布式队列,并借助ZooKeeper自带的特性,维护作业提交顺序、作业优先级、各节点负载情况等。借助ZooKeeper的PersistentSequentialZnode自动编号特性,可轻易实现一个简易的FIFO队列,在这个队列中,编号小的作业总算先于编号大的作业提交。
分布式系统很容易通过ZooKeeper实现负载均衡,典型的应用场景是分布式消息队列,在Kafka中,各个Broker和Consumer均会向ZooKeeper注册,保存自己的相关信息,组件之间可动态获取对方信息。
Broker和Consumer主要在ZooKeeper中写入以下信息: