开源组件系列(8):分布式结构化存储(Zookeeper)

目录

(一)分布式协调服务的意义

(二)Zookeeper数据模型

(三)Zookeeper基本架构

(四)Zookeeper应用案例:Leader选举


(一)分布式协调服务的意义

 

分布式协调服务在分布式应用中是不可缺少的,通过引入类似于文件系统的层级命名空间,并在此基础上提供一套简单易懂的规范语言,能够帮助用户轻易的实现诸如Leader选举、分布式锁及分布式队列等功能。Zookeeper已经广泛的应用在开源系统中,包括HDFS、Yarn、HBase等组件中。接下来通过Leader选举和负载均衡为例,说明分布式协调服务存在的意义和基本职责。

 

Leader选举:

在分布式系统中,常见的一种软件设计架构为Master/Slave,其中Master负责集群管理,Slave负责执行具体的任务(存储、处理数据等)。这种架构存在一种明显的缺陷:Master是单点,如果出现问题,将影响整个软件架构的使用。为了避免Master出现故障,常见的优化方案是引入多个Master,例如双Master模式:Active和Standby,如下图所示:

 

引入双Master需要解决两个问题:一个是如何选举出一个Master作为Active Master;另一个是如果Active Master出现了问题,如何将Standby Master安全切换为Active Master。为了避免单独开发分布式系统造成功能的冗余,因此引入Zookeeper提供基本可靠的协调服务势在必行,提供诸如选举和服务状态获取等基本功能。

 

负载均衡:

在Kakfa等分布式消息队列中,生产者将数据写入分布式队列,消费者从分布式消息队列中读取数据进行处理,因此就产生了两个基本问题:第一,由于消息队列是分布式的,节点的健康状态是动态变化的,因此生产者和消费者如何获得最新的消息队列位置?第二,消息队列提供了一组可存储数据的节点,如何让生产者将数据均衡地写入消息队列的各个节点?如下图所示:

为了解决以上两个问题,如果引入一个可靠的分布式协调服务,它需要具备简单的元信息存储和动态获取服务状态等功能。因此引入Zookeeper,可以有效的将服务协调的职责从分布式系统中独立出来,减少系统的耦合性。

 

(二)Zookeeper数据模型

 

考虑到分布式协调服务内部实现的复杂性,Zookeeper将尽可能简单的数据模型和API暴露给用户,以屏蔽协调服务本身的复杂性。Zookeeper提供了类似于文件系统的层级命名空间,而所有分布式协调功能均可以借助作用在该命名空间上的原语来实现。相关模型如下:

 

1.层级命名空间

下图给出了一个典型的Zookeeper层级命名空间,整个命名的方式类似于文件系统,以多叉树形式组织在一起。其中,每个节点被称为“znode”,主要包括以下几种属性:

(1)data:每个znode拥有一个数据域,记录了用户的数据,类型为字节数组,通过多副本的方式保证数据的存储可靠性;

(2)type:znode类型,具体分为持久化节点、临时节点、顺序增量三种类型;

(3)version:znode中的数据版本号,每次数据更新版本号将+1;

(4)children:znode可以包含子节点;

(5)ACL:znode访问控制列表,用户可以单独设置每个znode的可访问用户。

 

2.Watcher

Watcher是Zookeeper提供的发布/订阅机制,用户可以在某个znode上注册watcher以监听它的变化,一旦对应的znode被删除或者更新,Zookeeper将以事件的形式将变化内容发送给监听者。需要注意的是,Watcher一旦触发后便会被删除,除非该用户再次注册该Watcher。

 

3.Session

Session是Zookeeper中的一个重要概念,它是客户端与Zookeeper服务端之间通信通道,同一个Session中的消息是有序的。Session具有容错性:如果客户端连接的Zookeeper服务器宕机,客户端会自动连接到其他活着的服务器上。

 

(三)Zookeeper基本架构

 

Zookeeper服务通常由奇数个Zookeeper实例构成,其中一个实例为Leader角色,其他为Follower角色,它们同时维护了层级目录结构的一个副本,并通过ZAB协议维持副本之间的一致性。Zookeeper将所有数据保存在内存中,因而具有高吞吐率和低延迟的有点,如下图所示:

Zookeeper读写数据的路径如下:

读路径:任意一个Zookeeper实例均可以为客户端提供读服务,实例数目越多,读吞吐率越高;

写路径:任意一个Zookeeper实例均可以接受客户端的写请求,但需要进一步转发给Leader协调完成分布式写。Zookeeper采用了简化版的Paxos协议,也称之为ZAB协议,只要多数Zookeeper实例写成功,就认为本次写是成功的。如果一个集群存在2N+1个Zookeeper实例,只要其中的N+1个实例写成功,则本次写操作便成功了。从容错性角度看,集群最大容忍失败实例数目为N。

值得注意的是,Zookeeper实例数目越多,写延迟越高。当Leader出现故障时,Zookeeper会通过ZAB协议发起新一轮的Leader投票选举,保证集群中始终有一个可用的Leader。

Zookeeper中多个实例的内存数据并不是强一致的,ZAB协议只能保证同一时刻内至少多数节点中的数据是强一致的,为了让客户端读到最新的数据,需要给对应的Zookeeper实例发送通过指令,强制与Leader同步数据。

在Zookeeper集群中,随着Zookeeper实例数目的增多,读吞吐率升高,但写延迟也随之增加。为了解决集群扩展性导致的写性能下降问题,Zookeeper引入了第三个角色:Observer,Observer并不参与投票过程,其功能与Follower类似,可以接入正常的Zookeeper集群,接受并处理客户端读请求,或者将写请求进一步转发给Leader处理。由于Observer自身能够保存已存数据提供读服务,因此可以通过增加Observer实例数来提高系统的读吞吐率。

 

(四)Zookeeper应用案例:Leader选举

 

基于Zookeeper实现Leader选举的基本思想是,让各个参与竞选的实例同时在Zookeeper上创建指定的znode,谁创建成功则谁竞选成功,并将自己的信息(host、ip、port等)写入该znode数据域,之后其他竞选者向该znode注册watcher,便于当前Leader出现故障时,第一时间再次参与竞选,如下图所示:

基于Zookeeper的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选举。

原创文章 54 获赞 61 访问量 1万+

猜你喜欢

转载自blog.csdn.net/gaixiaoyang123/article/details/104689524