Zookeeper架构笔记

Zookeeper的需求目标

    分布式应用需要组成员管理,主结点选举,元数据(配置信息)管理,分布式锁等协同服务,Zookeeper的目标是为分布式应用提供了实现这些协同服务的中心化工具。同时,Zookeeper的目标还包括:

  • 高性能 支持高吞吐、低延时,以用于大型的分布式系统
  • 高可用 避免成为一个单点故障

    Zookeeper的目标不包括大量的数据存储,在设计采用Zookeeper的分布式应用时,应将用于协同控制的管理数据和应用数据分离,仅将少量的管理数据数据存储于Zookeeper中,将应用数据存储于等其它数据源中(如关系型数据库)。

Zookeeper的概念/术语

    ZNode:Zookeeper树状数据结构上的结点,可以存储少量数据。ZNode分为持久型、短暂型(Ephemeral)、顺序持久型和顺序短暂型。

    复制模式(Replicated Mode):Zookeeper可分为单机模式(Standalone Mode)和复制模式运行,复制模式时多台主机组成协作体,在这些主机间复制数据。

    协作体(Ensemble):用于复制Zookeeper数据的一组主机。协作体中有一个领导(Leader)和若干个追随者(Follower)。

   有效数(Quorum):在领导选举(Leader election)和原子化广播(Atomic broadcast)时,通过表决时所需的最小主机集合。Zookeeper默认使用的是多数有效数,即超过半数的主机集合。也支持权重有效数和分层级有效数,权重有效数指不同主机投票的权重不一样;分层级有效数将主机分为组,超过半数的组同意时通过,组内可采用权重投票。

    脑裂(Split Brain):一个协同体分裂为两个子集,各自独立的活动。分布式系统要防范脑裂引起数据不一致。

Zookeeper基础

设计思想

    Zookeeper不直接暴露Lock,Latch,Barrier等并发编程常用的原语,只提供了一组简单的API和保证,允许多个客户应用进程操作一个共享的树状数据结构。客户应用可以在此之上实现所需的并发控制服务。

层次化的数据模型

    如图所示,Zookeeper提供了一个树状的数据结构,树中的ZNode可以由其路径(path)来定位。在ZNode中可以存储少量的数据,如在/workers/worker-1 ZNode中存储了数据foo.com:2181。

基础API

  • create /path data 创建一个以/path命名的ZNode,并且包含数据data
  • delete /path 删除ZNode /path
  • exists /path 检测/path是否存在
  • setData /path data 将ZNode /path的值改为data
  • getData /path 返回ZNode /path的数据
  • getChildren /path 获得子结点的列表
  • sync 等待数据被同步到(客户端当前连接的)结点

不同类型的ZNode

  • 持久型ZNode:仅可以使用命令删除的ZNode
  • 短暂型ZNode:当创建结点的客户端Session失效时,将被自动删除的ZNode。短暂型ZNode也可以使用命令删除。短暂型ZNode不允许有子节点
  • 序列型ZNode:/path结尾将被指定唯一的,单调递增序号的ZNode。如创建一个序列型的ZNode /tasks/task-,则创建的ZNode可能名为/tasks/task-1。使用此类型,并发程序可以方便的创建出唯一名称地ZNode。序列型类型可以和持久型或短暂型类型组合,形成顺序持久型或顺序短暂型ZNode。

监视(Watch)和通知(Notification)

    在分布式应用协同服务中,得知其它进程的状态变化非常重要,为了避免客户端轮询带来的额外开销,Zookeeper提供了监视和通知机制。客户端可以在ZNode上设置一个监视;当ZNode变化时,监视将被触发并移除。监视被触发时,客户端将接收到一个通知包。同时,如果设置监视的客户端和Zookeeper的连接断开时,也会收到一个本地通知。

    在保证之外,使用监视和通知机制时需要注意一些点:

  • 监视是一次性的,监视被触发后即被移除,如果客户端要继续关注ZNode的变化,需要设置新的监视。
  • 为了避免ZNode在监视被移除和新的监视被设置期间发生变化,客户端要在读取值的同时设置监视。应注意到ZNode在监视被移除和新的监视被设置期间可能变化多次,设计应用时不可依赖于能看到每一个变化
  • 通知是异步发往设置监视的客户端的。当修改ZNode的客户端成功返回时,通知可能还没有到达设置监视的客户端。
  • 在复制模式时,Zookeeper不保证同一时刻两个客户端看到相同的数据,因此客户端在收到通知后,仍有可能读到旧值。如果要确保收到通知后读到修改后的值,应调用sync API。
  • 当客户端断开和Zookeeper的连接时,将不能得到任何通知,直到连接被重新连接。尚未收到通知的监视将收到session事件,程序在接到这类事件时应注意,在此种情形下,没有被通知不代表没有发生。

版本

    Zookeeper会为每一次更新操作设置一个版本号,客户端应用可利用版本号实现条件化操作,在锁的实现原理中,条件化操作和“乐观锁”是同义词。

架构概览

    在复制模式下,协同体(Ensemble)由一组Zookeeper的主机组成,其中有一个主节点(Leader)和若干个从节点(Follower)。每个节点中都运行着复制数据库(Replicated DB)和原子化广播(Atomic broadcast)组件,主节点上还运行着请求处理器(Request processor)组件。其中的复制数据库是包含了整个数据树的内存数据库,为了保证可恢复性,更新复制数据库之前,都会把更新操作记录在磁盘上。

    如图所示,客户端仅连接到协同体中的一个节点上,Zookeeper的数据不分区或分片,客户端读数据时直接从所连接节点的本地内存数据库获取。客户端的写请求由主节点统一处理,如果客户端连接的是从节点,其写请求将被转发到协同体中唯一的主节点,主节点通过原子广播协议将变更提交到协同体。主节点统一处理客户端的写请求,保证了写操作的全局有序性。

Zookeeper秘方(Recipes)

    通过前文,我们已经了解到Zookeeper提供了一个高性能、高可靠的分布式服务。Zookeeper还提供了三个应用:命名服务,配置管理和组成员管理。在此之外,可以以Zookeeper为基础,在客户端实现主结点选举、分布式锁等协同服务,在Zookeeper的官方文档中,以秘方的方式给出了如何实现这些协同服务的思路。需要注意的是,Apache Curator项目已经实现了多数的秘方,直接使用可以减少开发和调试的成本。下面,一起来学习几个秘方,看看Zookeeper是如何做到“以小博大”的

栅栏(Barriers)

    分布式系统使用栅栏以阻塞一组节点,直到满足一定条件时才解除阻塞,节点可继续执行。使用Zookeeper实现的方案如下:

  1. (一个)客户端C0创建一个ZNode '_barrier_node_',作为栅栏
  2. (多个)客户端Wn执行到栅栏检查行代码,调用exists API,检查'_barrier_node_'是否存在,同时设置一个Watch
  3. Wn调用exists API时,如果返回false,说明栅栏已经移除,则继续执行栅栏检查行之后的代码
  4. Wn调用exists API时,如果返回true,说明栅栏存在,则阻塞代码执行,等待'_barrier_node_'变化时被通知
  5. '_barrier_node_'变化时,Wn被通知,重新调用exists API,执行3~4步逻辑
  6. (一个)客户端C1判断栅栏解除条件已达到,删除'_barrier_node_'

分布式锁(Distributed Lock)

    采取前述栅栏的方法也可以实现一个锁:客户端Wn创建一个'_lock_node_',如果创建成功则占有锁;如果创建时遇到KeeperException.NodeExists异常,说明别的客户端已占有锁,则在'_lock_node_'上设置一个Watch,当得到通知时再尝试创建'_lock_node_'。这个简单的锁机制有“羊群效应”的问题,当有很多客户端在等待锁时,释放锁时会带来较大的并发压力。Zookeeper秘方给出的实现方案如下:

  1. 调用create API,创建一个路径为'_lock_node_/lock-'的顺序短暂型ZNode
  2. 调用getChildren '_lock_node_',设Watch以防羊群效应;如果当前客户端所创建的ZNode为序号最小,则当前客户端获得锁,执行完逻辑后删除创建的ZNode
  3. 对比当前节点序号次小的节点调用exists方法,同时设置Watch。因部分客户端可能退出了锁等待,lock-N的“序号次小”节点不一定是lock-(N-1),当lock-(N-1)不存在时可能是lock-(N-2),依次类推
  4. 如果3步的调用失败,或者收到了需要次小节点变化的Watch消息,则继续执行第2步

    此方案的最大优点是等待锁的客户端仅在一个ZNode上设置了Watch,可以避免所释放引起羊群效应。

主结点选举(Leader Election)

    可以认为主节点是获取了一个分布式锁的节点,可以使用上述的分布式锁秘方实现主节点选举。    

Zookeeper内部原理

事务和zxid(Zookeeper Transaction Identifier)

    如前所述,协同体中的主节点统一处理客户端的写请求(create, setData, delete)。在处理时,主节点将写请求转换为一个事务单元来处理。这里所谈的事务重点有两个特性:一是原子性,事务内的操作要么全成功要么全失败,不存在部分成功,例如,在setData事务中包含设置ZNode的数据和ZNode的版本号操作。二是幂等性(idempotent),同一个事务执行多次,结果仍然和执行一次时相同。

    主节点创建一个事务时,会为事务生成一个唯一的64位长整数作为zxid,zxid的前32位为世代(epoch),只有选举出新的leader后才被更新;后32位为计数器(counter),每一个新的事务都会单调递增。

快速主结点选举算法(Fast Leader Election)

    Zookeeper服务启动时进入LOOKING状态,如果协同体中的所有节点都是LOOKING状态,则每个节点都向其它节点发出主节点选举通知(Leader Election Notification)消息,开始主节点选举流程。注意:主节点选举只在协同体中的有效数子集中进行,以防止脑裂引起数据不一致。除了发送消息的节点的标识,主节点选举通知消息主要包含了该节点的“当前选票(Vote)”,为了便于理解问题,在这里把选票描述为一个数组(sid, zxid),sid为服务器的标识,zxid为在sid服务器最后执行的事务标识,sid暂时只考虑计数器部分。例如(1,5)标识当前选票是投给服务器1的,服务器1最后执行的事务zxid是5。节点接收到其它节点的主节点选举通知消息时:

  1. 记当前选票为(currSid, currZxid),收到的选票为(voteSid, voteZxid),记录sid的选票为(voteSid, voteZxid)。选举开始时,当前选票初始化为节点自己的sid和zxid。
  2. 如果currZxid<voteZxid,则当前选票更新为(voteSid, voteZxid),向所有其它节点发送新的主节点选举通知消息
  3. 如果currZxid=voteZxid,且currZxid<voteZxid,,则当前选票更新为(voteSid, voteZxid),向所有其它节点发送新的主节点选举通知消息

    选举过程持续,如果一个节点收到了有效数节点的选票一致,则发出消息声明主节点已经选出。如果选出的主节点为自己,则进入主节点模式;如果选出的主节点为其它节点,则进入从节点模式,尝试连接主节点复制数据。请看以下的实际例子:

    从上面的时序分析还可以发现,同样的初始条件可能产生不同的选举结果,如果s3迟迟没有收到s1的票,它会在收到s2的票后等待finalizeWait时间,然后选择自己,进入主节点模式。

    之所以被称为快速主节点选举算法,是因为早期的选举算法是通过轮询获得其它节点的状态,轮询的周期为1s。而新算法使用了消息广播的模式,只有在声明主节点前等待一个finalizeWait时间,默认为200ms,可以比旧算法更快的选出主节点。

原子化广播(Atomic Broadcasting)

    Zookeeper的写操作使用ZAB(Zookeeper Atomic Broadcasting)协议来实现,ZAB是一个类似于两阶段提交的协议,如图所示:

  1. 主节点发送一个PROPOSAL消息到所有的从节点
  2. 每个从节点收到PROPOSAL消息后,发送一个ACK消息给主节点,通知主节点它接受了修改提议
  3. 当主节点接收到有效数个节点的ACK消息后,给所有节点发送COMMIT消息,通知所有节点提交数据。计算有效数时包含主节点自己

    从对于ZAB协议原理的学习,我们可以理解前文所述的,Zookeeper不保证同一时刻两个客户端看到相同的数据。这里设计了ZAB协议而没有使用传统的两阶段提交,是为了平衡数据的一致性和高性能。

参考资料

1. https://www.safaribooksonline.com/library/view/zookeeper/9781449361297

2. http://zookeeper.apache.org/doc/current

3. https://www.tutorialspoint.com/zookeeper/zookeeper_workflow.htm

4. http://curator.apache.org/curator-recipes/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值