本文来说下Zab协议,这个协议是Zookeeper保证数据一致性的核心。
文章目录
概述
ZAB 协议是 Zookeeper 专门设计的一种支持崩溃恢复的原子广播协议。ZAB 协议是 ZooKeeper 的数据一致性和高可用解决方案。
ZooKeeper 并没有直接采用 Paxos 算法,而是采用了名为 ZAB 的一致性协议。ZAB 协议不是 Paxos 算法,只是比较类似,二者在操作上并不相同。
ZAB 协议定义了两个可以无限循环的流程:
- 选举 Leader:用于故障恢复,从而保证高可用。
- 原子广播:用于主从同步,从而保证数据一致性。
选举 Leader
ZooKeeper 的故障恢复
ZooKeeper 集群采用一主(称为 Leader)多从(称为 Follower)模式,主从节点通过副本机制保证数据一致。
- 如果 Follower 节点挂了 - ZooKeeper 集群中的每个节点都会单独在内存中维护自身的状态,并且各节点之间都保持着通讯,只要集群中有半数机器能够正常工作,那么整个集群就可以正常提供服务。
- 如果 Leader 节点挂了 - 如果 Leader 节点挂了,系统就不能正常工作了。此时,需要通过 ZAB 协议的选举 Leader 机制来进行故障恢复。
ZAB 协议的选举 Leader 机制简单来说,就是:基于过半选举机制产生新的 Leader,之后其他机器将从新的 Leader 上同步状态,当有过半机器完成状态同步后,就退出选举 Leader 模式,进入原子广播模式。
术语
myid
myid:每个 Zookeeper 服务器,都需要在数据文件夹下创建一个名为 myid 的文件,该文件包含整个 Zookeeper 集群唯一的 ID(整数)。
zxid
zxid:类似于 RDBMS 中的事务 ID,用于标识一次更新操作的 Proposal ID。为了保证顺序性,该 zkid 必须单调递增。因此 Zookeeper 使用一个 64 位的数来表示,高 32 位是 Leader 的 epoch,从 1 开始,每次选出新的 Leader,epoch 加一。低 32 位为该 epoch 内的序号,每次 epoch 变化,都将低 32 位的序号重置。这样保证了 zkid 的全局递增性。
服务器状态
- LOOKING:不确定 Leader 状态。该状态下的服务器认为当前集群中没有 Leader,会发起 Leader 选举。
- FOLLOWING:跟随者状态。表明当前服务器角色是 Follower,并且它知道 Leader 是谁。
- LEADING:领导者状态。表明当前服务器角色是 Leader,它会维护与 Follower 间的心跳。
- OBSERVING:观察者状态。表明当前服务器角色是 Observer,与 Folower 唯一的不同在于不参与选举,也不参与集群写操作时的投票。
选票数据结构
每个服务器在进行领导选举时,会发送如下关键信息:
- logicClock:每个服务器会维护一个自增的整数,名为 logicClock,它表示这是该服务器发起的第多少轮投票。
- state:当前服务器的状态。
- self_id:当前服务器的 myid。
- self_zxid:当前服务器上所保存的数据的最大 zxid。
- vote_id:被推举的服务器的 myid。
- vote_zxid:被推举的服务器上所保存的数据的最大 zxid。
投票流程
自增选举轮次
Zookeeper 规定所有有效的投票都必须在同一轮次中。每个服务器在开始新一轮投票时,会先对自己维护的 logicClock 进行自增操作。
初始化选票
每个服务器在广播自己的选票前,会将自己的投票箱清空。该投票箱记录了所收到的选票。例:服务器 2 投票给服务器 3,服务器 3 投票给服务器 1,则服务器 1 的投票箱为(2, 3), (3, 1), (1, 1)。票箱中只会记录每一投票者的最后一票,如投票者更新自己的选票,则其它服务器收到该新选票后会在自己票箱中更新该服务器的选票。
发送初始化选票
每个服务器最开始都是通过广播把票投给自己。
接收外部投票
服务器会尝试从其它服务器获取投票,并记入自己的投票箱内。如果无法获取任何外部投票,则会确认自己是否与集群中其它服务器保持着有效连接。如果是,则再次发送自己的投票;如果否,则马上与之建立连接。
判断选举轮次
收到外部投票后,首先会根据投票信息中所包含的 logicClock 来进行不同处理:
- 外部投票的 logicClock 大于自己的 logicClock。说明该服务器的选举轮次落后于其它服务器的选举轮次,立即清空自己的投票箱并将自己的 logicClock 更新为收到的 logicClock,然后再对比自己之前的投票与收到的投票以确定是否需要变更自己的投票,最终再次将自己的投票广播出去。
- 外部投票的 logicClock 小于自己的 logicClock。当前服务器直接忽略该投票,继续处理下一个投票。
- 外部投票的 logickClock 与自己的相等。当时进行选票 PK。
选票 PK
选票 PK 是基于(self_id, self_zxid)与(vote_id, vote_zxid)的对比:
- 外部投票的 logicClock 大于自己的 logicClock,则将自己的 logicClock 及自己的选票的 logicClock 变更为收到的 logicClock。
- 若 logicClock 一致,则对比二者的 vote_zxid,若外部投票的 vote_zxid 比较大,则将自己的票中的 vote_zxid 与 vote_myid 更新为收到的票中的 vote_zxid 与 vote_myid 并广播出去,另外将收到的票及自己更新后的票放入自己的票箱。如果票箱内已存在(self_myid, self_zxid)相同的选票,则直接覆盖。
- 若二者 vote_zxid 一致,则比较二者的 vote_myid,若外部投票的 vote_myid 比较大,则将自己的票中的 vote_myid 更新为收到的票中的 vote_myid 并广播出去,另外将收到的票及自己更新后的票放入自己的票箱。
统计选票
如果已经确定有过半服务器认可了自己的投票(可能是更新后的投票),则终止投票。否则继续接收其它服务器的投票。
更新服务器状态
投票终止后,服务器开始更新自身状态。若过半的票投给了自己,则将自己的服务器状态更新为 LEADING,否则将自己的状态更新为 FOLLOWING。
通过以上流程分析,我们不难看出:要使 Leader 获得多数 Server 的支持,则 ZooKeeper 集群节点数必须是奇数。且存活的节点数目不得少于 N + 1 。
每个 Server 启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的 server 还会从磁盘快照中恢复数据和会话信息,zk 会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。
原子广播(Atomic Broadcast)
ZooKeeper 通过副本机制来实现高可用。
那么,ZooKeeper 是如何实现副本机制的呢?答案是:ZAB 协议的原子广播。
ZAB 协议的原子广播要求:
所有的写请求都会被转发给 Leader,Leader 会以原子广播的方式通知 Follow。当半数以上的 Follow 已经更新状态持久化后,Leader 才会提交这个更新,然后客户端才会收到一个更新成功的响应。这有些类似数据库中的两阶段提交协议。
在整个消息的广播过程中,Leader 服务器会每个事物请求生成对应的 Proposal,并为其分配一个全局唯一的递增的事务 ID(ZXID),之后再对其进行广播。
Zookeeper Service网络结构
Zookeeper的工作集群可以简单分成两类,一个是Leader,唯一一个,其余的都是follower,如何确定Leader是通过内部选举确定的。
Leader和各个follower是互相通信的,对于Zookeeper系统的数据都是保存在内存里面的,同样也会备份一份在磁盘上。如果Leader挂了,Zookeeper集群会重新选举,在毫秒级别就会重新选举出一个Leader。集群中除非有一半以上的Zookeeper节点挂了,Zookeeper Service才不可用。
Zookeeper读写数据
写数据,一个客户端进行写数据请求时,如果是follower接收到写请求,就会把请求转发给Leader,Leader通过内部的Zab协议进行原子广播,直到所有Zookeeper节点都成功写了数据后(内存同步以及磁盘更新),这次写请求算是完成,然后Zookeeper Service就会给Client发回响应。
读数据,因为集群中所有的Zookeeper节点都呈现一个同样的命名空间视图(就是结构数据),上面的写请求已经保证了写一次数据必须保证集群所有的Zookeeper节点都是同步命名空间的,所以读的时候可以在任意一台Zookeeper节点上。
Zab协议
Zookeeper的核心是广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab(ZooKeeper Atomic Broadcast)原子消息广播协议作为数据一致性的核心算法,Zab协议是专为Zookeeper设计的支持崩溃恢复原子消息广播算法。
Zab协议核心如下:所有的事务请求必须一个全局唯一的服务器(Leader)来协调处理,集群其余的服务器称为follower服务器。Leader服务器负责将一个客户端请求转化为事务提议(Proposal),并将该proposal分发给集群所有的follower服务器。之后Leader服务器需要等待所有的follower服务器的反馈,一旦超过了半数的follower服务器进行了正确反馈后,那么Leader服务器就会再次向所有的follower服务器分发commit消息,要求其将前一个proposal进行提交。
Zab模式
Zab协议包括两种基本的模式:崩溃恢复和消息广播。
当整个服务框架启动过程中或Leader服务器出现网络中断、崩溃退出与重启等异常情况时,Zab协议就会进入恢复模式并选举产生新的Leader服务器。
当选举产生了新的Leader服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后,Zab协议就会退出恢复模式,状态同步是指数据同步,用来保证集群在过半的机器能够和Leader服务器的数据状态保持一致。
当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个服务框架就可以进入消息广播模式。
当一台同样遵守Zab协议的服务器启动后加入到集群中,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么加入的服务器就会自觉地进入数据恢复模式:找到Leader所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。
Zookeeper只允许唯一的一个Leader服务器来进行事务请求的处理,Leader服务器在接收到客户端的事务请求后,会生成对应的事务提议并发起一轮广播协议,而如果集群中的其他机器收到客户端的事务请求后,那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。
消息广播
Zab协议的消息广播过程使用是一个原子广播协议,类似一个2PC提交过程。具体的:
ZooKeeper使用单一主进程Leader用于处理客户端所有事务请求,并采用Zab的原子广播协议,将服务器数据状态变更以事务Proposal的形式广播Follower上,因此能很好的处理客户端的大量并发请求。
另一方面,由于事务间可能存在着依赖关系,Zab协议保证Leader广播的变更序列被顺序的处理,有些状态的变更必须依赖于比它早生成的那些状态变更。
最后,考虑到主进程Leader在任何时候可能崩溃或者异常退出, 因此Zab协议还要Leader进程崩溃的时候可以重新选出Leader并且保证数据的完整性;Follower收到Proposal后,写到磁盘,返回Ack。Leader收到大多数ACK后,广播Commit消息,自己也提交该消息。Follower收到Commit之后,提交该消息。
Zab协议简化了2PC事务提交:
- 去除中断逻辑移除,follower要么ack,要么抛弃Leader。
- Leader不需要所有的Follower都响应成功,只要一个多数派Ack即可。
崩溃恢复
上面我们讲了Zab协议在正常情况下的消息广播过程,那么一旦Leader服务器出现崩溃或者与过半的follower服务器失去联系,就进入崩溃恢复模式。
恢复模式需要重新选举出一个新的Leader,让所有的Server都恢复到一个正确的状态。
本文小结
本文详细分析了zookeeper保持数据一致性的原理。