ZooKeeper并没有直接采用Paxos算法,而是采用了一种被称为ZAB(ZooKeeper Atomic Broadcast)的一致性协议。
关于Paxos协议,可以看看我之前写的这篇:理解 Paxos 协议——浅谈分布式一致性协议
ZooKeeper的ZAB协议
ZAB协议是为分布式协调服务ZooKeeper专门设计的一种支持崩溃恢复的原子广播协议。
ZooKeeper使用一个单一的主进程来接受并处理客户端的所有事物请求,并采用ZAB的原子广播协议,将服务器数据的状态变更以事物Proposal的形式广播到所有的副本进程上去。
ZAB协议的核心是定义了对于那些会改变ZooKeeper服务器数据状态的事物请求的处理方式,即:
所以事物请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为Leader服务器,而余下的其他服务器则称为Follower服务器。Leader服务器负责将一个客户端事物请求转换成一个事物Proposal(提案),并将该Proposal分发给集群中所有的Follower服务器。
之后Leader服务器需要等待所有Follower进行正确的反馈之后,那么Leader服务器就会再次向Follower分发Commit消息,表示将前一个proposal进行提交。
协议介绍
ZAB协议包括两种基本模式:崩溃恢复和消息广播。
当整个服务框架在启动过程中,或是Leader服务器出现崩溃、网络中断、重启或者集群中不存在过半的服务器与Leader服务器保持正常通信等异常情况时,ZAB就会进入崩溃恢复模式并选举产生新的Leader服务器。
当选举产生了新的Leader服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步(数据同步)之后,ZAB协议就会退出恢复模式。
之后就进入了消息广播模式。如果此时一个同样遵循ZAB协议的加入到集群时,那么如果此时已经存在一个Leader在负责消息广播,那么该服务器就会自觉进入数据恢复模式,在完成之后加入到消息广播流程中。
ZooKeeper设计成只允许唯一的一个Leader服务器来进行事物请求的处理。Leader服务器在接收到客户端的事物请求之后,会生成对应提案并发起一轮广播;如果是其他非Leader机器接收到客户端的事物请求,那么它会将这个事物请求转发给Leader服务器。
消息广播
ZAB的消息广播过程是一个原子广播协议,类似与一个2PC的过程。
不过其整个流程与2PC也略有不同。在ZAB的二阶段提交过程中,移除了中断逻辑,所有的Follower服务器要么正常反馈Leader提出的事物Proposal,要么抛弃Leader服务器。同时在收到过半Follower的ack之后,Leader服务器就可以开始提交事物Proposal了,不必等待所有Follower都反馈。
当然,这种简化了的二阶段提交模型下,是无法处理因Leader服务器崩溃退出而带来的数据不一致问题的,因此在ZAB协议中添加了崩溃恢复模式来解决这个问题。
另外,整个消息广播协议是基于具有FIFO特性的TCP协议进行网络通信的,因此能够很容易的保证消息广播过程中消息接受与发送的顺序性。
在消息广播过程中,Leader服务器会为每个事物Proposal分配一个全局单调递增且唯一的ID,称为事物ID(即ZXID)。每个事物Proposal都必须按照ZXID的先后顺序来进行处理。Leader服务器会为每一个Follower服务器分配一个单独的队列,然后将需要广播的事物Proposal放入队列,并依据FIFO进行消息发送。Follower在接收到Proposal后,会以事物日志的形式写入本地磁盘,并在写入完之后反馈给Leader一个ack。在Leader接受半数Follower发送的ack之后,就会广播一个commit,同时自身也会完成对事物的提交。
崩溃恢复
一旦 Leader 服务器出现崩溃或者由于网络原因导致 Leader 服务器失去了与过半 Follower 的联系,那么就会进入崩溃恢复模式。
在整个过程中,需要一个高效的Leader选举算法快速选举出一个新的Leader服务器,同时还要让其他服务器快速感知新产生的Leader。
崩溃恢复主要包括两部分:Leader选举 和 数据恢复
ZAB协议崩溃恢复需要满足两个要求:
- 崩溃恢复过程需要确保已经被Leader提交的Proposal也能被所有Proposal提交
- 崩溃恢复过程需要丢弃那些在Leader上提出但未提交的事物
因此,选举出的Leader也必须满足以下要求:
- 该Leader节点持有最大的ZXID
- 不能包含未提交的Proposal(即新Leader是已经提交了所有Proposal的原Follower节点)
在完成Leader选举后,Leader服务器首先会确认事物日志中所有的Proposal是否都已经被集群中过半的机器提交了,即是否完成数据同步。
Leader服务器需要确保所有Follower服务器能接收到每一条事物Proposal,并且能正确地将所有已经提交的事物Proposal应用到内从数据库中,Leader服务器会通过前文问题到的队列,将每个Follower队列中没有被commit的事物以Proposal消息的形式按顺序发送给Follower,并在该Proposal消息后紧跟一个commit,表示该事物已提交。
来看看ZAB协议是如何处理那些被丢弃的事物Proposal的。在事物编号ZXID的设计中,ZXID是一个64位数字,低32位可以看做是一个简单的计数器,高32位代表了Leader周期epoch的编号,每次变更一个Leader,都会在原epoch上加1,这样在就旧的Leader恢复之后,其他Follower就不会听它的了,因为Follower只服从epoch最高的Leader命令。
ZAB与Paxos算法的联系与区别
ZAB协议并不是Paxos算法的一个典型实现,先看看两者的联系:
- 两者都存在一个类似于Leader进程的角色,负责协调多个Follower进程的运行
- Leader进程都会等待超半数的Follower做出正确反馈后,才会将一个提案提交
- ZAB的每个Proposal都包含了一个epoch,在Paxos对应的是Ballot,其含义都是一样的
在Paxos算法中,一个新选举的主进程会进行两个阶段的工作,首先新的Leader会通过和其他进程通信来收集上一个Leader提出的提案并将其提交,然后再开始提出自己的提案。ZAB在Paxos的基础上添加了一个同步阶段。
而Paxos算法与ZAB协议在其本质上的设计目标也不太一样:
- ZAB-->高可用的分布式数据主备系统
- Paxos-->分布式的一致性状态机系统
参考资料
- 书籍《从 Paxos 到 ZooKeeper——分布式一致性原理与实践》