Zookeeper的ZAB协议
ZAB(Zookeeper Atomic Broadcast) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
二、ZAB 协议介绍
ZAB 协议包含两种基本模式,分别是崩溃恢复和原子广播。需要注意的是:leader 节点可以处理事务请求和非事务请求,follower 节点只能处理非事务请求,如果 follower 节点接收到非事务请求,会把这个请求转发给 leader 服务器。
三、消息广播的原理(zxid):消息广播的过程实际上是一个简化版本的二阶段提交过程。
这里需要注意的是: leader 的投票过程,不需要 Observer 的 ack,也就是 Observer 不需要参与投票过程,但是 Observer 必须要同步 Leader 的数据从而在处理请求的时候保证数据的一致性,用来实现提升读性能。
四、崩溃恢复的实现原理
1. 已经被处理的消息不能丢:当 leader 收到合法数量 follower 的 acks 后,就向各个 follower 广播 COMMIT 命令,同时也会在本地执行 COMMIT 并向连接的客户端返回 acks 。但是如果在各个 follower 在收到 COMMIT 命令前 leader 就挂了,导致剩下的服务器并没有执行都这条消息。即 leader 在发 commit 命令前挂了(此时 follower 已经将该消息存入本地,只是没有 commit ),导致 follower 没有收到这条已经被 leader 处理的消息。
2. 被丢弃的消息不能再次出现:当 leader 接收到消息请求生成 proposal 后就挂了,其他 follower 并没有收到此 proposal,因此经过恢复模式重新选了 leader 后,这条消息是被跳过的。 此时之前挂了的 leader 重新启动并注册成了 follower,他保留了被跳过消息的proposal 状态,与整个系统的状态是不一致的,需要将其删除。
2. zxid 是 64 位,高 32 位是 epoch 编号,每经过一次 Leader 选举产生一个新的 leader,新的 leader 会将 epoch 号+1,低 32 位是消息计数器,每接收到一条消息这个值+1,新 leader 选举后这个值重置为 0,这样设计的好处在于老的 leader 挂了以后重启,它不会被选举为 leader,因此此时它的 zxid 肯定小于当前新的 leader。当老的 leader 作为 follower 接入新的 leader 后,新的 leader 会让它将所有的拥有旧的 epoch 号的未被 COMMIT 的 proposal 清除。
选主:leader选举是zk中最重要的技术之一,也是保证分布式数据一致性的关键所在。当集群中的一台服务器处于如下两种情况之一时,就会进入leader选举阶段——服务器初始化启动、服务器运行期间无法与leader保持连接。 选举阶段,集群间互传的消息称为投票,投票Vote主要包括二个维度的信息:ID、ZXID
- ID:被推举的leader的服务器ID,集群中的每个zk节点启动前就要配置好这个全局唯一的ID。
- ZXID:被推举的leader的事务ID ,该值是从机器DataTree内存中取的,即事务已经在机器上被commit过了。
节点进入选举阶段后的大体执行逻辑如下:
- (1)设置状态为LOOKING,初始化内部投票Vote (id,zxid) 数据至内存,并将其广播到集群其它节点。节点首次投票都是选举自己作为leader,将自身的服务ID、处理的最近一个事务请求的ZXID(ZXID是从内存数据库里取的,即该节点最近一个完成commit的事务id)及当前状态广播出去。然后进入循环等待及处理其它节点的投票信息的流程中。
- (2)循环等待流程中,节点每收到一个外部的Vote信息,都需要将其与自己内存Vote数据进行PK,规则为取ZXID大的,若ZXID相等,则取ID大的那个投票。若外部投票胜选,节点需要将该选票覆盖之前的内存Vote数据,并再次广播出去;同时还要统计是否有过半的赞同者与新的内存投票数据一致,无则继续循环等待新的投票,有则需要判断leader是否在赞同者之中,在则退出循环,选举结束后根据选举结果及各自角色切换状态,leader切换成LEADING、follower切换到FOLLOWING、observer切换到OBSERVING状态。
伪代码:
if (接收到的投票的选举周期 > 本服务器当前的选举周期) { // 修改本服务器的选举周期为接收到的投票的选举周期 // 清空本服务器的投票箱(表示选举周期落后,重新开始投票) // 比较接收到的选票所选择的服务器与本服务器的数据谁更新,本服务器将选票投给数据较新者 // 发送选票 } else if(接收到的投票的选举周期 < 本服务器当前的选举周期){ // 接收到的投票的选举周期落后了,本服务器直接忽略此投票 } else if(选举周期一致) { // 比较接收到的选票所选择的服务器与本服务器当前所选择的服务器的数据谁更新,本服务器将选票投给数据较新者 // 发送选票 }
ZooKeeper集群在进行领导者选举的过程中不能对外提供服务