ZAB协议
ZAB 全称 Zookeeper Atomic Broadcast,Zookeeper 原子消息广播协议。
ZAB 是一种专门为 Zookeeper 设计的一种支持 崩溃恢复 的 原子广播协议 ,是 Zookeeper 保证数据一致性的核心算法。ZAB 借鉴了 Paxos 算法,但它不是通用的一致性算法,是特别为 Zookeeper 设计的。
消息传播
ZAB 协议的消息广播使用原子广播协议, 类似一个二阶段提交的过程 ,但又有所不同。
第一阶段,
leader提一个proposal给follower。第二阶段,
leader发送commit,通知follower完成事务的提交。和一般
2PC不一样,不需要全部follower回复ack,超过半数ack就可以才进入第二阶段。
主节点选取
一些概念
election epoch
这是分布式系统中极其重要的概念,由于分布式系统的特点,无法使用精准的时钟来维护事件的先后顺序,因此,Lampert 提出的 Logical Clock 就成为了界定事件顺序的最主要方式。
其实就是表示选举的轮数。每次选主,都会加一轮。类似于每个皇帝的年号。
zxid
每个消息的编号。
全局唯一。由两部分组成:高32位是 epoch,低32位是 epoch 内的自增id,由0开始。每次选出新的 leader,epoch 会递增,同时 zxid 的低32位清0。
节点状态
LOOKING: 节点正处于选主状态,不对外提供服务,直至选主结束FOLLOWING: 作为系统的从节点,接受主节点的更新并写入本地日志LEADING: 作为系统主节点,接受客户端更新,写入本地日志并复制到从节点类似
raft的leader, follower, candidate三角色
选主时机
节点启动时
leader会给follower发心跳,如果follower长期未收到leader的心跳,会进入选主如果
leader发现多数follower不再响应自己,那自己很可能是掉线了,leader也会进入LOOKING状态,导致其他点进入选主
选举条件
选
epoch最大的epoch相等,选zxid最大的epoch和zxid都相等,选server_id最大的(zoo.cfg中配置的myid)节点可以投多次票,给不同的参选者
需要过半数节点投票同意,才能选出主节点。主节点收到半数以上票后会通知其他点
选主后的数据同步
这是和 Paxos/raft 比较不同的地方。Paxos 没有这步。
新的 leaader 选出后,可能进度比别的 follower 都快,也可能比部分 follower 慢。
对
leader上有、follower上没有的数据,leader要重新同步给follower对
leader上没有、follower上有的,leader要通知follower丢弃
通过同步阶段,保证数据的一致性。
其他
选主期间,集群不可用
关于一致性的讨论
ZAB 的算法是强一致性的,读写都走 leader。
但 zk 实际应用中,根据配置及使用的接口,是有不同的一致性表现的:
它的写是集中在
leader上的,因此写一定是线性一致性的。zk默认读走follower的,可能读到过期数据,这时不是线性的。这种情况下,
zk的一致性,介于 顺序一致性 和 线性一致性之间。(between sequential consistency and linearizability)。zk提供了sync接口,强制follower从leader同步数据,保证强一致性,但是会加重leader负担。
和 Paxos / raft 的异同
理论:
Paxos/raft是理论,ZAB是具体实现。两者有很多相似的地方,比如日志复制、超半数节点同意、纪元的概念等。设计目标:两者的设计目标也不同,
Paxos/raft用于构建一个复制状态机,而zk是用于构建一个主备系统。选主:
同步:
ZAB选主完成后有一个数据同步过程,而Paxos/raft没有这个过程。raft是通过之后的一致性检查来完成同步。效率:
raft的节点只能投一次票,可能导致各参选者票数一样,选不出主。raft通过加了个随机过期时间减少该情况ZAB的节点在轮次内可以投多张票,只要遇到更大的票就更新,然后分发新的投票给所有人。这样不会出现split vote现象,但会导致选举更慢。
选主时机的触发:
raft只有follower收不到心跳时会进入选主ZAB在此基础上,还有在leader收不到大部分follower心跳时转为LOOKING状态的机制
投票:对于投票这个动作,
raft是单播,投票者只回复给拉票者;ZAB是广播,投票者会将投票信息告知所有节点主节点上任:
raft由主节点通知其他节点ZAB投票时就是广播,节点收到其他点的投票决定后就能知道哪个点收到半数以上的票了
日志复制:
主节点复制日志给从节点,都是过半数从节点收到即可
raft是连续性日志,有indexZAB不是连续性的。leader需要为每一个follower单独保存一个队列,记录所有的改动。这样导致zk在同步数据时,需要阻塞leader的写,完成同步后再解锁
安全:
新旧主节点同时向从节点写的脑裂问题:
raft的从节点通过term号,拒绝旧leader的写入请求ZAB的从节点选出新leader后就会断开与旧leader的连接
选主后的旧数据:
raft:leader不允许直接提交之前任期的日志。只有当当前任期日志过半了,才会顺便将之前任期的日志进行提交ZAB:采取激进的策略,对于所有过半还是未过半的日志都判定为提交,同步给其他从节点
新节点加入:
raft的新节点会直接收到leader的同步请求。zk则会发起新一轮选主,但会被其他节点拒绝,从而变为FOLLWING状态
Paxos下各节点收到的日志后可能是乱序的,但能通过共识算法保证的执行顺序的一致。而主备系统则要求严格的顺序性保证。
zk VS etcd
两者都是通用的一致性元信息存储,也都有 watch 来用于变更通知和分发,就这两点,在应用服务的大部分场景下都可以互相替代。
zk 开发的进度和版本更新比较慢了,社区活跃度远远不如 etcd。而且应用性上,如果两者都用过的话,应该都会觉得 etcd 易用性更高 (restful api)。
其他要考虑周边产品的生态问题了,运维和开发如果是 Java 应用更多,那毫无疑问应该用 ZK。如果是搞 Go 的,那么还是 etcd 吧,毕竟有时候遇到问题还是要看源码的。
参考
Last updated