本文主要讲述Zookeeper的内部原理以及ZAB协议,Zookeeper算是大数据中的一个协作框架,比较简单,本文应该是Zookeeper部分的最后一篇文章。关注专栏《破茧成蝶——大数据篇》,查看更多相关的内容~
目录
一、Zookeeper的内部原理
1.1 节点类型
1.2 Stat结构体
1.3 监听器
1.4 写数据流程
1.5 Zookeeper的选举机制
二、ZAB协议
2.1 ZAB协议是什么
2.2 ZAB协议的作用
2.3 ZAB协议的原理
2.4 ZAB协议的工作
2.4.1 消息广播
2.4.2 崩溃恢复
2.5 ZAB数据同步
Zookeeper的节点类型可以分为两种:持久型和短暂型。持久型指当客户端与服务器断开连接后,该节点不会删除;短暂型指当客户端与服务器断开连接后,该节点也会随之删除。
Stat结构体在《二十一、Zookeeper的命令行操作》中已经有提到过了,但是这里得再次不厌其烦的说一下。
(1)cZxid:创建节点的事务zxid。每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。
(2)ctime:znode被创建的毫秒数(从1970年开始)。
(3)mZxid:znode最后更新的事务zxid。
(4)mtime:znode最后修改的毫秒数(从1970年开始)。
(5)pZxid:znode最后更新的子节点zxid。
(6)cversion:znode子节点变化号,znode子节点修改次数。
(7)dataVersion:znode数据变化号。
(8)aclVersion:znode访问控制列表的变化号。
(9)ephemeralOwner:如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0。
(10)dataLength:znode的数据长度。
(11)numChildren:znode子节点数量。
1、首先在main()线程中创建Zookeeper客户端,这时会同时创建两个线程:一个用于网络连接通信(A),一个用于监听(B)。
2、通过A线程将注册的监听事件发送个Zookeeper,这时Zookeeper会将注册的监听事件添加到注册监听器列表。
3、Zookeeper监听到有数据或者路径等等的变化,就会将这个消息发送给B线程。
1、客户端向Zookeeper的服务器上写数据,首先会发送一个写请求。
2、如果接收到该请求的服务器(A)不是Leader服务器,那么它会将这个请求转发给Leader服务器,这时Leader服务器会将请求广播给集群内的其他服务器。各个服务器会将写请求加入待写队列,并向Leader服务器发送成功的信息。
3、当Leader服务器收到半数以上的Follower服务器的成功信息后,说明该写操作可以执行。这是Leader会向各个Follower发送提交信息,各个Follower收到信息后,会落实队列里面的写请求,此时写入成功。
4、服务器A通知客户端数据写入成功。
选举机制遵循半数机制,即:集群中半数以上机器存活,集群就是可用状态,所以Zookeeper适合安装奇数台服务器。Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的。例如有五台服务器(A-E)组成的Zookeeper集群,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依次启动,那么会发生下面的情况:
1、服务器A启动,发起一次选举。服务器A投自己一票。此时服务器A票数一票,不够半数以上(3票),选举无法完成,服务器A状态保持为LOOKING。
2、服务器B启动,再发起一次选举。服务器A和B分别投自己一票并交换选票信息:此时服务器A发现服务器B的ID比自己目前投票推举的(服务器A)大,更改选票为推举服务器B。此时服务器A票数0票,服务器B票数2票,没有半数以上结果,选举无法完成,服务器A、B状态保持LOOKING。
3、服务器C启动,发起一次选举。此时服务器A和B都会更改选票为服务器C。此次投票结果:服务器A为0票,服务器B为0票,服务器C为3票。此时服务器C的票数已经超过半数,服务器C当选Leader。服务器A、B更改状态为FOLLOWING,服务器C更改状态为LEADING。
4、服务器D启动,发起一次选举。此时服务器A、B、C已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器C为3票,服务器D为1票。此时服务器D服从多数,更改选票信息为服务器C,并更改状态为FOLLOWING。
5、服务器E启动,过程同4。
ZAB协议的全称是Zookeeper Atomic Broadcast(Zookeeper原子广播)。Zookeeper是通过ZAB协议来保证分布式事务的最终一致性。ZAB协议是为分布式协调服务Zookeeper专门设计的一种支持崩溃恢复的原子广播协议,是Zookeeper保证数据一致性的核心算法。ZAB借鉴了Paxos算法,但又不像Paxos算法那样,是一种通用的分布式一致性算法。它是特别为Zookeeper设计的支持崩溃恢复的原子广播协议。
在Zookeeper中主要依赖ZAB协议来实现数据一致性,基于该协议,Zookeeper实现了一种主备模型(即Leader和Follower模型)的系统架构来保证集群中各个副本之间数据的一致性。这里的主备系统架构模型,就是指只有一台客户端(Leader)负责处理外部的写事务请求,然后Leader客户端将数据同步到其他Follower节点。Zookeeper客户端会随机的链接到Zookeeper集群中的一个节点,如果是读请求,就直接从当前节点中读取数据;如果是写请求,那么节点就会向Leader提交事务,Leader接收到事务提交,会广播该事务,只要超过半数节点写入成功,该事务就会被提交。
ZAB协议的特性:
1、ZAB协议需要确保那些已经在Leader服务器上提交的事务最终被所有的服务器提交。
2、ZAB协议需要确保丢弃那些只在Leader上被提出而没有被提交的事务。
1、当主进程出现异常的时候,整个Zookeeper集群依旧能够正常工作。
2、使用一个Leader来接收并处理客户端的事务请求,并采用ZAB的原子广播协议,将服务器数据的状态变更以事务proposal(事务提议)的形式广播到所有的Follower进程上去。
3、保证一个全局的变更序列被顺序引用。Zookeeper是一个树形结构,很多操作都要先检查才能确定是否可以执行,为了保证这一点,ZAB要保证同一个Leader发起的事务要按顺序被请求,同时还要保证只有先前Leader的事务被请求之后,新选举出来的Leader才能再次发起事务。
ZAB协议要求每个Leader都要经历三个阶段:发现、同步、广播。
发现阶段要求Zookeeper集群必须选举出一个Leader,同时Leader会维护一个Follower可用客户端列表。将来客户端可以和这些Follower节点进行通信。同步阶段Leader要负责将本身的数据与Follower完成同步,做到多副本存储。Follower将队列中未处理完的请求消费完成后,写入本地事务日志中。广播阶段Leader可以接受客户端新的事务Proposal请求,将新的Proposal请求广播给所有的Follower。
ZAB协议定义了事务请求的处理方式:所有的事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被叫做Leader服务器。其他剩余的服务器则是Follower服务器。Leader服务器负责将一个客户端事务请求,转换成一个事务Proposal,并将该Proposal分发给集群中所有的Follower服务器,也就是向所有 Follower节点发送数据广播请求。分发之后Leader服务器需要等待所有Follower服务器的反馈,在ZAB协议中,只要超过半数的Follower服务器进行了正确的反馈后,那么Leader就会再次向所有的Follower服务器发送Commit消息,要求其将上一个事务proposal进行提交。这也就是上文中1.4提到的写数据流程。
当整个集群启动过程中,或者当Leader服务器出现网络中弄断、崩溃退出或重启等异常时,ZAB协议就会进入崩溃恢复模式,选举产生新的Leader。当选举产生了新的Leader,同时集群中有过半的机器与该Leader服务器完成了状态同步(即数据同步)之后,ZAB协议就会退出崩溃恢复模式,进入消息广播模式。这时,如果有一台遵守ZAB协议的服务器加入集群,因为此时集群中已经存在一个Leader服务器在广播消息,那么该新加入的服务器自动进入恢复模式:找到Leader服务器,并且完成数据同步。同步完成后,作为新的Follower一起参与到消息广播流程中。
当Leader出现崩溃退出或者机器重启,亦或是集群中不存在超过半数的服务器与Leader保存正常通信,ZAB协议就会再一次进入崩溃恢复,发起新一轮Leader选举并实现数据同步。同步完成后又会进入消息广播模式,接收事务请求。在整个消息广播中,Leader会将每一个事务请求转换成对应的proposal来进行广播,并且在广播事务Proposal之前,Leader服务器会首先为这个事务Proposal分配一个全局单递增的唯一ID,称之为事务ID(即zxid),由于ZAB协议需要保证每一个消息的严格的顺序关系,因此必须将每一个proposal按照其zxid的先后顺序进行排序和处理。
消息广播的步骤如下:
1、客户端发起一个写操作请求。
2、Leader服务器将客户端的请求转化为事务Proposal提案,同时为每个Proposal分配一个全局的ID,即zxid。
3、Leader服务器为每个Follower服务器分配一个单独的队列,然后将需要广播的Proposal依次放到队列中去,并且根据FIFO策略进行消息发送。
4、Follower接收到Proposal后,会首先将其以事务日志的方式写入本地磁盘中,写入成功后向Leader反馈一个Ack响应消息。
5、Leader接收到超过半数以上Follower的Ack响应消息后,即认为消息发送成功,可以发送commit消息。
6、Leader向所有Follower广播commit消息,同时自身也会完成事务提交。Follower接收到commit消息后,会将上一条事务提交。
Zookeeper采用ZAB协议的核心,就是只要有一台服务器提交了Proposal,就要确保所有的服务器最终都能正确提交Proposal。Leader服务器与每一个Follower服务器之间都维护了一个单独的FIFO消息队列进行收发消息,使用队列消息可以做到异步解耦。Leader和Follower之间只需要往队列中发消息即可。如果使用同步的方式会引起阻塞,性能要下降很多。
一旦Leader服务器出现崩溃或者由于网络原因导致Leader服务器失去了与过半Follower的联系,那么就会进入崩溃恢复模式。在ZAB协议中,为了保证程序的正确运行,整个恢复过程结束后需要选举出一个新的Leader服务器。
ZAB协议崩溃恢复要求满足两个要求:1、确保已经被Leader提交的Proposal必须最终被所有的Follower服务器提交。2、确保丢弃已经被Leader提出的但是没有被提交的Proposal。根据上述要求ZAB协议需要保证选举出来的Leader满足以下条件:1、新选举出来的Leader不能包含未提交的Proposal。即新选举的Leader必须都是已经提交了Proposal的Follower服务器节点。2、新选举的Leader节点中含有最大的zxid。
1、完成Leader选举后新的Leader具有最高的zxid,在正式开始工作之前(接收事务请求,然后提出新的Proposal),Leader服务器会首先确认事务日志中的所有的Proposal是否已经被集群中过半的服务器Commit。
2、Leader服务器需要确保所有的Follower服务器能够接收到每一条事务的Proposal,并且能将所有已经提交的事务Proposal应用到内存数据中。等到Follower将所有尚未同步的事务Proposal都从Leader服务器上同步过来并且应用到内存数据中以后,Leader才会把该Follower加入到真正可用的Follower列表中。
在ZAB的事务编号zxid设计中,zxid是一个64位的数字。其中低32位可以看成一个简单的单增计数器,针对客户端每一个事务请求,Leader在产生新的Proposal事务时,都会对该计数器加1。而高32位则代表了Leader周期的epoch编号。epoch编号可以理解为当前集群所处的年代或者周期。每次Leader变更之后都会在epoch的基础上加1,这样旧的Leader崩溃恢复之后,其他Follower也不会听它的,因为Follower只服从epoch最高的Leader命令。每当选举产生一个新的Leader,就会从这个Leader服务器上取出本地事务日志中最大编号Proposal的zxid,并从zxid中解析得到对应的epoch编号,然后再对其加1,之后该编号就作为新的epoch值,并将低32位数字归零,由0开始重新生成zxid。ZAB协议通过epoch编号来区分Leader变化周期,能够有效避免不同的Leader错误的使用了相同的zxid编号提出了不一样的Proposal的异常情况。基于以上策略当一个包含了上一个Leader周期中尚未提交过的事务Proposal的服务器启动时,当这台机器加入集群中,以Follower角色连上Leader服务器后,Leader服务器会根据自己服务器上最后提交的Proposal来和Follower服务器的Proposal进行比对,比对的结果肯定是Leader要求Follower进行一个回退操作,回退到一个确实已经被集群中过半机器Commit的最新Proposal。
到这本文基本上接近尾声了,这篇文章理论描述居多,可能会比较枯燥,你们有什么问题,欢迎留言,让我看看你们都有哪些问题~