分布式专题——19 Zookeeper分布式一致性协议ZAB源码剖析
1 ZAB 协议
-
Zookeeper Atomic Broadcast(Zookeeper 原子广播协议):
- ZAB 是 Paxos 算法的简化实现,并非直接采用 Paxos 来解决分布式一致性
- 专门为分布式协调服务 Zookeeper 设计,支持崩溃恢复和原子广播这两个关键能力,Zookeeper 正是基于该协议实现功能;
-
基于 ZAB 协议,Zookeeper 采用**主备模式(Leader-Follower 模式)**的系统架构,以此保持集群中各个副本之间的数据一致性;
-
客户端所有写入数据的操作,都先发送到 Leader 节点;
-
接着由 Leader 负责将数据复制到 Follower 节点,从而保证集群内数据的一致性;
- 数据复制过程(类似两阶段提交 2PC,但有优化);
- ZAB 协议在数据复制时,只需要 Follower(包含 Leader 自己的 ack)有一半以上返回 Ack 信息,就可以执行提交操作;
-
这种优化大大减小了同步阻塞,同时也提高了可用性;
-
-
ZAB 协议的两种核心模式(Zookeeper 在这两种模式间切换):
-
消息广播模式:当 Leader 服务可以正常使用时,Zookeeper 进入消息广播模式,进行正常的数据广播等操作;
-
崩溃恢复模式:当 Leader 服务不可用时,Zookeeper 进入崩溃恢复模式,以恢复集群的正常服务能力。
-
2 消息广播模式
-
ZAB 协议的消息广播过程采用原子广播协议,流程类似两阶段提交(2PC):
-
客户端的写请求,全部由 Leader 节点接收;
-
Leader 会将请求封装成事务 Proposal,并发送给所有 Follower 节点;
-
之后根据 Follower 的反馈,若超过半数(包含 Leader 自己)的节点成功响应,Leader 就会执行
commit
操作;
-
-
消息广播的具体流程:
-
Leader 收到客户端(如创建节点等)请求后:
- 将请求封装成事务Proposal,并发送给所有 Follower;
- 写本地数据文件,同时给自己发送 ack(确认);
-
Follower 收到 Proposal 后:
- 写本地数据文件;
- 向 Leader 返回 ack;
-
当 Leader 收到半数以上的 ack时:
- 向 Follower 发送 commit 指令;
- 向 Observer 发送 inform,Observer 负责存储消息(Observer 不参与投票等关键决策,主要用于扩展读能力);
- 执行 commit,写自己的内存数据;
- 回发节点数据变动通知给客户端(触发客户端监听事件),最后返回客户端命令操作结果;
-
Follower 收到 commit 指令后,会写自己的内存数据执行 commit;
-
-
细节补充
-
Leader 收到客户端请求后,会将请求封装成事务,并为事务分配全局递增的唯一 ID(ZXID)。ZAB 协议需保证事务顺序,因此会按 ZXID 对事务进行先后排序后处理,这一排序主要通过消息队列实现;
-
Leader 和 Follower 之间存在消息队列,用于解耦二者的耦合关系,消除同步阻塞问题;
-
Zookeeper 集群为保证所有进程能有序顺序执行,只有 Leader 服务器能接受写请求。若 Follower 服务器接收到客户端的写请求,会转发给 Leader 处理,Follower 仅能处理读请求;
-
ZAB 协议规定,若一个事务在一台机器上被 commit 成功,那么该事务应该在所有机器上都被 commit 成功,即便有机器出现故障崩溃也不例外。
-
3 崩溃恢复模式
-
当 Leader 崩溃(即 Leader 失去与过半 Follower 的联系)时,Zookeeper 会进入崩溃恢复模式。此时需要解决 Leader 崩溃后的数据一致性问题,比如:
-
假设 1:Leader 在复制数据给所有 Follower 后,还没收到 Follower 的 ack 就崩溃;
-
假设 2:Leader 收到 ack 并提交自己,且发送了部分 commit 后崩溃;
-
-
ZAB 协议为崩溃恢复定义的两个原则
-
原则 1:ZAB 协议会丢弃那些只在 Leader 提出,但没有提交的事务;
-
原则 2:ZAB 协议确保那些已经在 Leader 提交的事务,最终会被所有服务器提交;
-
-
ZAB 协议设计了下面这个选举算法,用于崩溃恢复时新 Leader 的选举:
- 该选举算法要能确保提交已经被 Leader 提交的事务,同时丢弃已经被跳过的事务;
- 为满足这一要求,选举出的新 Leader 需要拥有集群中所有机器里 ZXID(事务 ID,全局递增唯一)最大的事务;
- 这样做的好处是:可以省去 Leader 服务检查事务的提交和丢弃工作这一步操作,提升恢复效率。
4 数据同步
-
触发时机:崩溃恢复完成后,在 Leader 正式工作(接收客户端请求)之前,需要进行数据同步;
-
目的:
- Leader 要先确认事务是否已经被过半的 Follower 提交,也就是完成数据同步,从而保持数据一致;
- 当 Follower 完成数据同步后,Leader 会将这些 Follower 加入到可用服务器列表中;
-
实际上,Leader 服务器处理或丢弃事务都是依赖 ZXID ,那么这个 ZXID 如何生成呢?
-
ZXID 是 ZAB 协议中用于标识事务的编号,它是一个 64 位的数字,结构分为两部分;
-
高 32 位:
- 代表了每代 Leader 的唯一性。它来源于 Leader 服务器上取出的本地日志中最大事务 Proposal 的 ZXID,并从中解析出对应的
epoch
值(Leader 选举周期)。当一轮新的选举结束后,这个值会加一,且事务 ID 会从 0 开始自增; - 高 32 位能让 Follower 识别不同的 Leader,简化了数据恢复流程;
- 代表了每代 Leader 的唯一性。它来源于 Leader 服务器上取出的本地日志中最大事务 Proposal 的 ZXID,并从中解析出对应的
-
低 32 位:
-
代表了每代 Leader 中事务的唯一性。对于客户端的每一个事务请求,Leader 都会生成一个新的事务 Proposal,并对该计数器进行 +1 操作;
-
低 32 位保证了每代 Leader 下事务的唯一性,结合高 32 位,可全局唯一标识事务,为事务的有序处理和数据同步等提供依据;
-
-
-
数据同步的具体操作:当 Follower 连接上 Leader 之后,Leader 服务器会根据自己服务器上最后被提交的 ZXID和 Follower 上的 ZXID 进行比对,然后根据比对结果,要么回滚相关事务,要么和 Leader 进行同步,以确保数据一致性。