文章目录
- Zookeeper数据同步
- 一、ZAB协议
- 1、介绍
- 2、消息广播的实现原理
- 二、崩溃恢复原理
- 1、为什么有崩溃恢复模式
- 2、崩溃恢复设计猜想
- a、已经被处理的消息不能丢
- b、被丢弃的消息不能再次出现
- 总结
- 三、关于ZXID
Zookeeper数据同步
zk通过三种不同的 集群角色来组成整个高性能集群,在zk中,客户端会随机连接到zk集群中的一个节点,如果是读请求,就直接从当前节点中读取数据,如果是写请求,那么请求会被转发给leader提交事务,然后leader会广播事务,只要超过半数节点写入成功,那么写请求就会被提交(类2PC事务,2PC是全数节点ok)
- 问题:
- 集群的leader节点如何选举出来?
- leader节点崩溃以后,整个集群无法处理写请求,如何快速从其他节点里面选出出新的leader呢?
- 原leader节点临时网络故障,恢复后脑裂怎么解决?
- leader节点和各个follower节点的数据一致性如何保证?
一、ZAB协议
Zookeeper Atomic Broadcast,协议是为分布式协调服务 Zookeeper 专门设计的一种支持崩溃恢复的原子广播协议。在 Zookeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,Zookeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
1、介绍
包含两种基本模式,分别是 崩溃恢复、原子广播
当整个集群在启动时,或者当leader 节点出现网络中断、崩溃等情况时,ZAB协议就会进入恢复模式并选举产生新的leader,当leader服务器选举出来后,并且集群中有过半机器和该leader节点完成数据同步后(同步指得是数据同步,用来保证集群中过半的机器能够和leader服务器的数据状态保持一致)。ZAB协议就会退出恢复模式。
当集群中已经有过半的Follower 节点完成了和Leader 状态同步以后,那么整个集群就会进入消息广播模式。整个时候,在Leader 节点正常工作时,启动一台新的服务器加入到集群,那么这个服务器就会直接进入数据恢复模式,和Leader 节点进行数据同步。同步完成后即可正常对外提供非事务请求的处理。
需要注意的是:leader 节点可以处理事务请求和非事务请求,Follower 节点只能处理非事务请求,会把这个请求转发给Leader服务器
2、消息广播的实现原理
消息广播的过程实际上是一个简化版本的二阶段提交过程(可以去了解2PC和3PC协议)
// TODO 记得画图
流程如下:
- 1、leader 接受到消息请求后,将消息赋予给一个全局唯一的64位自增id,叫:zxid,通过zxid的代销比较即可以实现因果有序的这个特征
- 2、leader 为每个follower 准备了一个FIFO队列(通过TCP协议来实现,以实现了全局有序这个特点)将带有zxid的消息作为一个提案(proposal)分发给所有的follower
- 3、当follower接受到proposal,先把proposal写到磁盘,写入成功以后再向leader恢复一个ack
- 4、当leader 接受到合法数量(超过半数节点)的 ack,leader 就会向这些follower发送commit命令,同时会在本地执行该消息
- 5、当follower接受到消息的commit命令以后,就会提交该消息
注意1: 和完整的2PC事务不一样的地方在于,zab协议不能终止事务,follower节点要么ack给leader,要么抛弃leader,只需要保证过半数的节点响应这个消息并提交了即可,虽然在某一个时刻follower节点和leader节点的状态不一致,但是也是这个特性提升了集群的整体性能。当然这种数据不一致的问题,zab协议提供了一种恢复模式来进行数据恢复
注意2:leader的投票过程,不需要Observer 的ack,也就是Observer 不需要参与投票过程,但是Observer 必须要同步Leader 的数据从而在处理请求的时候保证数据的一致性
二、崩溃恢复原理
主要是Leader 节点崩溃 或由于 网络问题导致 Leader服务器失去了过半的Follower节点的联系,那么就会进入到崩溃恢复模式,这种模式下ZAB 需要做两件事
- 选举出新的Leader
- 数据同步
1、为什么有崩溃恢复模式
前面的指出消息广播时,知道ZAB协议的消息广播机制是简化版的2PC协议,这种协议只需要集群中过半的节点响应提交即可。但是它无法处理Leader 服务器崩溃带来的数据不一致问题。因此在ZAB协议中添加了 “崩溃恢复模式” 来解决这个问题
2、崩溃恢复设计猜想
在ZAB协议中的崩溃恢复需要保证,如果一个事务Proposal 在一台机器上被处理成功,那么这个事物应该在所有机器上都被处理成功,哪怕是出现故障。为了达到这个目的,我们先来设想一下,在zookeeper中会有哪些场景导致数据不一致,以及针对这个场景,zab协议中中的崩溃恢复咋处理
a、已经被处理的消息不能丢
当Leader 收到合法数量的 follower 的ACKs 后,就向各个follower 广播 commit 命令,同时也会在本地执行commit 并向连接的客户端返回[成功]。但是如果在各个follower 在收到commit 命令前leader 就挂了,导致剩下的服务器并没有执行到这条消息
图中的C2 就是一个典型的例子,在集群正常运行过程的某一个时刻,Server1 是leader 服务器,先后广播了消息P1、P2、C1、P3和C2。其中当leader 服务器把消息C2(Commit 事务 proposal2)发出后就立即崩溃退出了,那么针对这种情况,ZAB协议就需要确保事务Proposal2 最终能够在所有的服务器上都能被提交成功,否则将会出现不一致。
b、被丢弃的消息不能再次出现
当leader 接受到消息请求生成proposal 后就挂了,其他follower 并没有收到此proposal ,因此经过恢复模式重新选了leader后,这条消息是被跳过的。此时,之前挂了的leader重新启动并注册成了follower,他保留了被跳过消息的proposal状态,与整个系统的状态是不一致的,需要将其删除。
总结
ZAB协议需要满足上面两种情况,就必须要设计一个leader 选举算法;能够确保已经被leader提交的事务proposal 能够提交、同时丢弃已经被跳过的事务 proposal 。针对这个要求
1、如果leader 选举算法能够保证新选举出来的leader 服务器拥有集群中所有机器最高编号(ZXID)的事务Proposal,那么就可以保证这个新选举出来的leader一定具有已经提交的提案。因为所有提案被Commit 之前必须有超过半数的 follower ACK,即必须有超过半数节点的服务器的事务日志上有该提案的proposal ,因此,只要有合法数量的节点正常工作,就必须有一个节点保存了所有被commit 消息的proposal状态。
2、另外一个,zxid 是64 位,高32位是epoch编号,每经过一次Leader 选举产生一个新的leader,新的leader 会将epoch号 + 1,低32位是消息计数器,每接受到一条消息这个值 + 1,新leader 挂了以后重启,肯定小于当前新leader。当老的 leader 作为follower 接入新的leader 后,新的leader 会将所有的拥有旧的epoch 号的未被Commit 的proposal清除。
三、关于ZXID
前面一直提到的zxid,也就是事务id,那么这个id具体起到什么作用?以及这个id如何生成的?
为了保证事务的顺序一致性,zookeeper 采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal) 都在被提出的时候加上了zxid。实现中zxid 是一个64位的数字,
高32位是epoch(ZAB协议通过epoch 编号来区分 Leader周期变化的策略)用来标识leader 关系是否改变,每次一个leader被选出来,它都会有一个新的 epoch = (原来的epoch + 1),标识当前属于新的leader的统治时期。
低32位用于递增计数
epoch:可以理解为当前集群所处的年代或者周期,每个leader 就像皇帝,都有自己的年号,所以每次改朝换代,leader 变更之后,都会在前一个年代的基础上加1。这样就算旧的leader 崩溃恢复之后,也没有人听他的了,因为follower 只听从当前年代饿leader 的命令。