ZAB协议
简介
ZAB协议,全程Zookeeper Atomic Broadcast(Zookeeper 原子广播协议)它是专门为分布式协调服务zookeeper,设计的一种支持崩回和恢复和原子广播的协议
从设计上看,ZAB协议和Raft很类似
从设计上看ZAB协议和Raft很类似,Zookeeper集群中,只有一个Leader节点,其余均为Follower
三个阶段只行完为一个周期,在zookeeper集群的整个生命周期中,这三个阶段会不断进行,如果leader崩溃或因为其他原因导致Leader缺失,ZAB协议会再次进入阶段1
Zookeeper 使用一个单一主进程来接收并处理客户端的所有事务请求,即写请求。当服务器数据的状态发生变更后,集群采用 ZAB 原子广播协议,以事务提案 Proposal 的形式广播到所有的副本进程上。ZAB 协议能够保证一个全局的变更序列,即可以为每一个事务分配一个全局的递增编号 xid。 当 Zookeeper 客户端连接到 Zookeeper 集群的一个节点后,若客户端提交的是读请求,那么当前节点就直接根据自己保存的数据对其进行响应;如果是写请求且当前节点不是Leader,那么节点就会将该写请求转发给 Leader,Leader 会以提案的方式广播该写操作,只要有超过半数节点同意该写操作,则该写操作请求就会被提交。然后 Leader 会再次广播给所有订阅者,即通知它们同步数据。
核心
ZAB协议的核心:定义了事物请求的处理方式
- 所有的事务请求必须由全局唯一的服务器来协调处理,这样的服务器被叫做Leader服务器,其他剩余的服务器则是Follower服务器
- Leader服务器负责将一个客户端事务请求转换成一个事务proposal,并将proposal分发给集群中所有的follower服务器,也就是像所有Follower节点发送数据广播请求(或数据复制)
- 分发之后Leader服务器需要等待所有Follower服务器的反馈(ACK请求),在ZAB协议中,只要超过半数的Follower服务器进行正确的反馈之后(也就是收到半数以上的Follower的Ack请求),那么leader就会再次向所有的Follower服务器发送Commit消息,要求其将一个事务proposal进行提交
ZAB协议内容
zab协议包括俩种基本形式:崩溃恢复和消息广播
崩溃恢复
一旦Leader服务器出现崩溃或者由于网络原因导致Leader服务器失去了过半Follower的联系,那么就回崩溃恢复模式.
前面我们说过,崩溃恢复具有俩个阶段,Leader选举与初始化同步.当LEader选举与初始化同步.当完成Leader选举后,此时的Leader还是一个准Leader,要经过初始化同步后才能变为真正的Leader
角色
为了避免Zookeeper的单点问题,Zookeeper也是以集群形式出现的,Zookeeper集群中的角色主要有以下三类
- Leader:事物请求的唯一处理者,并且可处理读
- Follower:可处理读,不会处理事物请求.如果Leader挂掉,具备选举权和被选举权
- Observer:可处理读,不会处理事物请求,如果Leader挂掉,不具备选举权和被选举权.其目的在于提高集群请求处理的吞吐量
这三类角色在不同的情况下,又有一些不同的叫法
Learner:即Follower或者Observer
QuorumServer:leader或者Follower
三个数据
在ZAB中有三个很重要的数据
- zxid:64位long类型,高32位表示epoch,低32位表示xid
- epoch:时期.每个leader选举结束后会生成一个epoch,learner
- xid:事物id,流水记录号
三个阶段
整个ZAB协议一共定义了三个阶段
- 发现:要求zookeeper集群必须选出一个Leader进程,同时leader会维护一个Follower可用客户列表.将来客户端可用和这些Follower节点进行通信
- 同步:分为初始化同步和更新同步
- 广播:分为初始化广播和更新广播
三个ID
SID:服务器ID.用来唯一标识一台ZooKeeper集群中的机器,每台机器不能重复,和myid一致
ZXID:事物IDZXID是一个事物ID,用来标识一次服务器状态的变更.在某一个时刻,集群中的每台机器的ZXID不一定完全一致,这和Zookeeper服务器对于客户端更新请求的处理逻辑有关
Epoch:每个Leader任期的代号,没有leader同一轮投票过程中的逻辑时钟是相同的.每投完一次票,这个数据就会增加
同步模式和广播模式
初始化同步
当leader被选举之后还是一个准leader要其经过初始化同步后才能变为真正的leader
具体过程
- 为了保证Leader向learner发送提案的有序,Leader会为每一个Learner服务器准备一个队列
- Leader将那些没有被各个Learner同步的事物封装为proposal
- Leader 将这些 Proposal 逐条发给各个 Learner,并在每一个 Proposal 后都紧跟一个COMMIT 消息,表示该事务已经被提交,Learner 可以直接接收并执行
- Learner 接收来自于 Leader 的 Proposal,并将其更新到本地
- Follower更新成功后,会向准Leader发送ACK信息
- Leader服务器在收到来自Follower的ACK后就会将Follower加入到真正可用的Follower列表.没有反馈ACK,或反馈了但Leader没有收到的Follower,Leader不会将其加入到Follwer列表
消息广播算法
当即集群中有过半的follower完成了初始化状态同步,那么整个Zookeeper集群就进入到了正常工作模式
其他集群中的其他节点收到客户端的事物请求,那么这些Learner会将请求转发给Leader服务器,然后一次执行
- Leader接受事务请求后,为事物赋予一个全局唯一的64位自增id,即zxid,通过zxid的大小比较即可实现事物的有序管理,然后将事物封装为一个proposal
- Leader根据Follower列表获取到的所有Follower,然后再将Proposal通过做这些Follower的队列将提案发送给Follower
- Leader根据Follower接收到提案后,会先将提案的zxid与本地事物日志最大的zxid进行比较,若当前提案的 zxid 大于最大 zxid,则将当前提案记录到本地事务日志中,并向 Leader 返回一个 ACK
- 当Leader接受到过半的ACK后,Leader就会向所有Follower的队列发送Commit消息,向所有的Observer的队列发送Proposal
- 当Follower收到Commit消息后,就会将日志中的事物正式更新到本地.当Observer收到Proposal后,会直接将事物更新到本地
恢复模式
俩个原则
当集群正在启动过程中,或 Leader 与超过半数的主机断连后,集群就进入了恢复模式。对于要恢复的数据状态需要遵循两个原则。
处理过的请求不能丢
当 Leader 收到超过半数 Follower 的 ACK 后,就向各个 Follower 广播 COMMIT 消息,批准各个 Server 执行该写操作事务。当各个 Server 在接收到 Leader 的 COMMIT 消息后就会在本地执行该写操作,然后会向客户端响应写操作成功。但是如果在非全部 Follower 收到 COMMIT 消息之前 Leader 就挂了,这将导致一种后果:部分 Server 已经执行了该事务,而部分 Server 尚未收到 COMMIT 消息,所以其并没有执行该事务。当新的 Leader 被选举出,集群经过恢复模式后需要保证所有 Server 上都执行了那些已经被部分 Server 执行过的事务。
已被丢弃的消息不能再现
当 Leader 接收到事务请求并生成了 Proposal,但还未向任何 Follower 发送时就挂了,因此,其他 Follower 根本就不知道该 Proposal 的存在。当新的 Leader 选举出来,整个集群进入正常服务状态后,之前挂了的 Leader 主机重新启动并注册成为了 Follower。若那个别人根本不知道的 Proposal 还保留在那个主机,那么其数据就会比其它主机多出了内容,导致整个系统状态的不一致。所以,该 Proposal 应该被丢弃。类似这样应该被丢弃的事务,是不能再次出现在集群中的,应该被清除。
Leader选举
当集群正在启动过程中,或 Leader 与超过半数的主机断连后,集群就进入了恢复模式。而恢复模式中最重要的阶段就是 Leader 选举。
myid : 这是 zk 集群中服务器的唯一标识,称为 myid。例如,有三个 ZooKeeper 服务器,那么编号分别是1,2,3。
逻辑时钟 : Logicalclock,是一个整型数,该概念在选举时称为 logicalclock,而在选举结
束后称为 epoch。即 epoch 与 logicalclock 是同一个值,在不同情况下的不同名称。
ZooKeeper 状态 : ZooKeeper 集群中的每一台主机,在不同的阶段会处于不同的状态。每一台主机具有四种状态。
- LOOKING
- FOLLOWING
- OBSERVING
- LEADING
Leader选举算法
若进行 Leader 选举,则至少需要两台主机,这里以三台主机组成的集群为例。
在集群初始化阶段,当第一台服务器 Server1 启动时,其会给自己投票,然后发布自己的投票结果。投票包含所推举的服务器的 myid 和 ZXID,使用(myid, ZXID)来表示,此时 Server1的投票为(1, 0)。由于其它机器还没有启动所以它收不到反馈信息,Server1 的状态一直属于Looking,即属于非服务状态。
当第二台服务器 Server2 启动时,此时两台机器可以相互通信,每台机器都试图找到
Leader,选举过程如下:
- 每个 Server 发出一个投票。此时 Server1 的投票为(1, 0),Server2 的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自 LOOKING 状态的服务器。处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行 PK,PK
规则如下:- 优先检查 ZXID。ZXID 比较大的服务器优先作为 Leader。
- 如果 ZXID 相同,那么就比较 myid。myid 较大的服务器作为 Leader 服务器。
- 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息。对于 Server1、Server2 而言,都统计出集群中已经有两台主机接受了(2, 0)的投票信息,此时便认为已经选出了新的 Leader,即 Server2。
- 改变服务器状态。一旦确定了 Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为 FOLLOWING,如果是 Leader,就变更为 LEADING。
- 添加主机。在新的 Leader 选举出来后 Server3 启动,其想发出新一轮的选举。但由于当前集群中各个主机的状态并不是 LOOKING,而是各司其职的正常服务,所以其只能是以Follower 的身份加入到集群中。
断连后的 Leader 选举
- 在 Zookeeper 运行期间,Leader 与非 Leader 服务器各司其职,即便当有非 Leader 服务器宕机或新加入时也不会影响 Leader。但是若 Leader 服务器挂了,那么整个集群将暂停对外服务,进入新一轮的 Leader 选举,其过程和启动时期的 Leader 选举过程基本一致。
- 假设正在运行的有 Server1、Server2、Server3 三台服务器,当前 Leader 是 Server2,若某一时刻 Server2 挂了,此时便开始新一轮的 Leader 选举了。选举过程如下:
- 变更状态。Leader 挂后,余下的非 Observer 服务器都会将自己的服务器状态由FOLLOWING 变更为 LOOKING,然后开始进入 Leader 选举过程。
- 投票。每个 Server 会发出一个投票,仍然会首先投自己。不过,在运行期间每个服务器上
的 ZXID 可能是不同,此时假定 Server1 的 ZXID 为 111,Server3 的 ZXID 为 333;在第一轮投票中,Server1 和 Server3 都会投自己,产生投票(1, 111),(3, 333),然后各自将投票发送给集群中所有机器。 - 接收来自各个服务器的投票。与启动时过程相同。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自 LOOKING 状态的服务器。
- 处理投票。与启动时过程相同。针对每一个投票,服务器都需要将别人的投票和自己的投票进行 PK。对于 Server1 而言,它的投票是(1, 111),接收 Server3 的投票为(3, 333)。其首先会比较两者的 ZXID,Server3 投票的 zxid 为 333 大于 Server1 投票的 zxid 的 111,于是Server1 更新自己的投票为(3, 333),然后重新投票。对于 Server3 而言,其无须更新自己的投票,只是再次向集群中所有主机发出上一次投票信息即可。
- 统计投票。与启动时过程相同。对于 Server1、Server2 而言,都统计出集群中已经有两台主机接受了(3, 333)的投票信息,此时便认为已经选出了新的 Leader,即 Server3。
- 改变服务器的状态。与启动时过程相同。一旦确定了 Leader,每个服务器就会更新
自己的状态。Server1 变更为 FOLLOWING,Server3 变更为 LEADING。
简单来说就是依次比较Epoch,ZXid,SID,选择最大的那个作为新leader
可用性与容错性
Zookeeper遵循的是CP原则保证了一致性,但牺牲了可用性.体现在哪呢
当 Leader 宕机后,ZooKeeper 集群会马上进行新的 Leader 的选举。但选举时长在 30-200 毫秒间,整个选举期间 ZooKeeper 集群是不接受客户端的读写操作的,即 ZooKeeper 集群是处于瘫痪状态的。所以不满足可用性
奇数与偶数
若出现超过半数的主机宕机,则投票永远无法通过。基于该理论,由 5 台主机构成的集群,最多只允许 2 台宕机。而由 6 台构成的集群,其最多也只允许 2 台宕机。即,6 台与5 台的容灾能力是相同的。基于此容灾能力和资源节省的原因,建议使用奇数台主机构成集群。(但从系统吞吐量上说,6 台主机的性能一定是高于 5 台的)9174
重启
- 整体重启:将整个集群停止,然后更新所有主机的配置后再次重启集群。该方式会使集群停止对外服务,所以该方式慎用。
- 部分重启: 每次重启一小部分主机。注意:不能多于半数,因为重启的主机过半,则无法进行选举,无法应对宕机或者写操作。