【Zookeeper】分布式原理与ZAB协议、顺序一致性保证

一、Zookeeper——分布式协调系统

Zookeeper是一个分布式的、开源的分布式应用程序协调服务。是Google的Chubby的一个开源实现,也是Hadoop和Hbase的重要组件。

Zookeeper的设计目的是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。

分布式应用程序可以基于它实现诸如:数据发布订阅、负载均衡、命名服务、分布式协调通知、集群管理、Master选举、分布式锁和分布式队列等功能。

二、分布式理论

分布式原理CAP

  • Consistency一致性:对于分布式的系统,客户读写的数据完全保证是最新的,不会发生两个不同的客户端,在不同的节点上读到的数据不一致的情况。
  • Availability可用性:客户端想要访问数据的时候,可以得到响应。(不同的系统会规定不同时长的响应时间,超出之后称为不可用)
  • Partition Tolerance分区容错性:数据存储在多个节点,可能将发生意料之外的网络分区。分区容错性要求,发生网络分区时,系统仍然可以提供服务,除非全部节点都挂掉。

既然是分布式系统,那么分区容错性是必须要保证的。所以矛盾点在于一致性与可用性的取舍。

最终一致性原理BASE

  • BasicallyAvailability基本可用:出现故障时,允许损失部分可用,如电商大促,为了应对访问量激增,部分用户被引导到降级页面
  • Soft-status软状态:允许系统存在中间状态,中间状态不会影响可用性
  • EventuallyConsistency最终一致性:经过一段时间之后,更新的数据将同步到各个节点中,这个时间称之为最终一致性的时间窗口。

BASE针对对大型的分布式系统,通过牺牲强一致性,获得高可用性。

分布式事务2PC、3PC

2PC二阶段提交

当一个事务跨越了多个分布式节点时,需要引入一个协调者来统一调度所有节点的执行逻辑。

  • 提交事务请求(投票阶段)
    1. 事务询问
    2. 执行事务:执行事务操作,并记录Undo和Redo日志
    3. 参与者向协调者反馈响应
  • 执行事务提交(执行阶段)
    1. 事务提交(所有参与者的响应均为yes)
      a. 发送提交请求
      b. 事务提交
      c. 反馈提交结果
      d. 事务完成
    2. 中断事务(任一节点反馈了no,或者有超时)
      a. 发送回滚请求
      b. 事务回滚
      c. 反馈回滚结果
      d. 事务中断

优点:原理简单、实现方便

缺点

  1. 同步阻塞:参与者在等待其他参与者的响应过程中,无法进行其他的任务操作,直到最后一个节点反馈完成
  2. 单点问题:如果协调者出现问题,整个协议将无法运行。如果在第二阶段出现问题,所有节点将处于锁定事务资源的状态
  3. 数据不一致:在阶段二,一部分参与者收到了提交请求,另一部分没有收到,将导致数据不一致
  4. 太过保守:当参与者出现故障无法反馈,协调者只能根据自身的超时机制来判断是否中断事务

3PC三阶段提交

与2PC不同的是,将「提交事务请求」一分为二,形成了cancommit,precommit,do commit三个阶段

  • canCommit
    1. 事务询问
    2. 参与者反馈响应
  • preCommit,根据反馈结果选择
    1. 执行预提交
      a. 发送预提交请求
      b. 事务预提交
      c. 响应反馈
    2. 中断事务
      a. 发送中断请求
      b. 中断事务
  • doCommit
    1. 执行提交
    2. 中断事务

一旦进入阶段三,可能会存在以下两种故障。

  1. 协调者出现问题
  2. 协调者和参与者之间的网络出现故障

无论出现那种情况,最终都会导致参与者无法及时接受到来自协调者的doCommit或者abort请求,针对这样的异常情况,参与者都会在等待超时之后,继续进行事务提交。

优点:降低参与者的阻塞范围、出现单点故障后继续达成一致

缺点:参与者接收到precommit消息后,如果出现网络分区,此时协调者所在的节点和参与者无法进行正常的网络通信,在这种情况下,该参与者依然会进行事务的提交,这必然出现数据的不一致性。

  • Q1. 3PC对2PC的改动点?

引入超时机制。同时在协调者和参与者中都引入超时机制。
在第一阶段和第二阶段中插入一个准备阶段,保证了在最后提交阶段之前各参与节点状态的一致。

  • Q2. 为什么要把投票阶段一分为二?

假设有1个协调者,9个参与者。其中有一个参与者不具备执行该事务的能力。
如果类似于2PC的处理方式:
协调者发出prepare消息之后,其余参与者都将资源锁住,直接执行事务,写入undo和redo日志。
协调者收到相应之后,发现有一个参与者不能参与。
所以,又出一个roolback消息。其余8个参与者,又对消息进行回滚。

这样子,是不是做了很多无用功?
所以,引入can-Commit阶段,主要是为了在预执行之前,保证所有参与者都具备可执行条件,从而减少资源浪费

三、ZAB协议

Zookeeper Atomic Broadcast (Zookeeper原子广播),ZAB借鉴了Paxos,是专门为Zookeeper设计的 支持崩溃恢复的原子广播协议

1. ZAB的特点

  • 广播消息:使用一个单一的主进程Leader来接收并处理客户的事务请求(写请求),将服务器的状态以事务proposal的形式广播到所有的Follwer中。

  • 顺序执行:保证一个全局的变更序列被顺序引用:同一个Leader发起的事务要按照顺序被reply。zab协议的有序性保证是通过几个方面来体现的:

    1. 服务之前用TCP协议进行通讯,保证在网络传输中的有序性;
    2. 节点之前都维护了一个FIFO的队列,保证全局有序性;
    3. 通过全局递增的ZXID保证因果有序性。
  • 崩溃恢复:在主机断电、机器宕机的情况下,集群恢复之后依然能够正常工作

2. ZK集群节点的角色

  • Leader :主节点,保证集群事务处理的顺序性。

  • Follower:从节点,处理非事务请求、转发事务请求给Leader、参与事务请求Proposal投票、参与Leader选举投票

  • Observer:跟随者,不参与任何投票,只处理非事务请求(读请求)

3. ZK集群的节点状态

  • Looking:选举状态

  • Leading:leader才有的状态

  • Following:follower才有的状态

4. ZAB协议的两种主要模式

  • 崩溃恢复(将选举新的Leader)

    1. 服务器启动集群新建时
    2. 主机与当前集群一半以上的机器失去联系后
  • 消息广播

    1. 选举出Leader服务器之后,进入消息广播模式,开始接收处理客户端的请求

5. ZXID:事务id

Leader服务器收到事务请求之后,先给此事务提议Proposal生成一个全局单调递增唯一id(ZXID),然后再发送给所有的从节点。

数据结构:是一个64位的数字。

  • 高32位:epoch(纪元),代表周期,每选举产生一个新的 Leader,服务器时就会取出其本地日志中最大事务的 ZXID ,解析出 epoch(纪元)值操作加 1作为新的 epoch ,并将低 32 位置零。
  • 低32位: counter(计数器),它是一个简单的单调递增的计数器,针对客户端的每个事务请求都会进行加 1 操作;

记录Leader周期的作用:发生异常时,集群中的其他 Follower 节点选举出了一个新的Leader。而如果旧的Leader又加入了集群,向其他节点发送请求,其他节点也会比较ZXID,发现epoch值比自身的小,因此忽略掉这个请求。

消息广播

类似于二阶段提交2PC:

  • 如果从节点收到写操作,会转发到主节点进行。主收到写操作时,先本地为事务生成ZXID,然后发给所有 Follower 节点事务提议(Propose)。
  • Follower 收到事务Propose时,先把提议事务的日志写到本地磁盘,成功后返回给Leader一个ACK。
  • Leader 收到过半ACK后,对事务提交,再给所有的Follower提发Commit命令;不必等待所有的节点回复。

在这里插入图片描述
与2PC不同的是,Leader只需要收到过半ACK就可以提交事务了,而不需要等收到所有的从节点反馈。

崩溃恢复

对于崩溃之前,Leader节点上事务可能在不同的阶段,要求:

1. 只在Leader上提出proposal的事务,最终会被丢弃

  • 场景
    Leader 接收到消息请求生成 Proposal后就挂了,其他Follower 并没有收到此Proposal,因此新选出的Leader中必然不含这条消息。
    需要保证的是,假如之前挂了的Leader 重新启动,只能成为Follower,而且要删除自己上旧的Proposal。

  • 选举
    Zab 通过 ZXID的高32位 来实现这一目的,即使旧的Leader 挂了后重启,它也不会被选举为Leader,因为此时它的 ZXID 肯定小于当前的新Leader。

  • 数据恢复
    当旧的 Leader 以 Follower 的身份上线之后,新Leader会以自己 最后被提交的Proposal 为准,明确其他节点需要舍弃的事务,Leader会要求该台机器进行回滚操作,回滚到某个被半数机器执行的最新的事务版本。

2. 已经在Leader上Commit的事务,最终会被所有的节点提交

  • 场景
    Leader 收到大多数ACK之后,先在 Leader上执行,然后向各个 Follower 发送Commit命令。如果有部分 Follower收到了Commit,一部分没有收到,此时Leader就已经挂了,导致没收到的节点没有执行这条事务。
    我们需要确保重新选举Leader之后,这条已经在原Leader上提交的事务不会丢失。

  • 选举
    Leader挂掉之后,选举 ZXID最大 的Follower作为新Leader。
    因为一个事务Propose被主节点Commit之前,必须已经收到大多数的 Follower 的ACK,即大多数服务器已经将该 proposal写入日志文件。
    此时选zxid最大的节点,这个 Follower 节点必然存有所有被Commit的事务Proposal。

  • 数据恢复
    Leader为每一个Follower都准备一个队列,并将那些没有被各Follower同步的事务以Proposal消息的形式逐个发送给Follower,并在每个Proposal消息后面紧跟一个Commit消息表示该事务已经被Leader提交。
    等到某个follower同步了所有之前尚未同步的事务并将其成功应用到本地数据库,Leader会将该Follower加入到可用Follower列表中。

Fast Leader Election 快速选举算法

  1. 每个进入到 Looking状态的节点,先投票给自己,然后把投票信息发给其他节点。投票信息包括:

    • 当前选举轮数epoch(每次选举都把轮数+1,防止不同的选举互相干扰)
    • 被投节点的zxid(事务Id)
    • 被投节点的编号myId(集群中每个节点的标志Id)
  2. 其他 Looking状态的节点收到后,比较大小:

    • 比较epoch
    • 比较zxid
    • 比较myID
    • 每收到一个投票,都查看已收到的投票记录列表,是否有节点已经达到一半以上的投票数。
      如果已经有,则终止投票,宣布选举结束,更新自身状态,然后进入到数据同步、消息广播阶段。

Leader选举步骤

服务器启动时期的Leader选举:

  1. 每一个Server发出一个投票
    初始情况,都会讲自己作为Leader进行投票
  2. 接收来自各个服务器的投票
    每个服务器都会接收来自其他服务器的投票
  3. 处理投票
    接收到其他服务器的投票之后,针对每一个投票,都将别人的与自己的票按照上述算法进行比较
  4. 变更投票
    发现其他的投票 > 自己,则更新自己的投票为对方投票,并且出去将投票发出去。
  5. 统计投票
    每次投票后,服务器都会统计所有投票,判断是否已经有过半的机器接收到相同的投票信息。如果过半,则认为已经选出了Leader
  6. 改变服务器状态
    一旦确定了Leader,每个服务器就会更新自己的状态:如果是Follower,那么久变更为FOLLOWING,如果是Leader,那么久变更为LEADING。

四、一致性保证

聊一聊ZooKeeper的顺序一致性

上文中ZAB的两阶段提交保证了最终一致性,除此之外,Zookeeper还需要实现顺序一致性。即,集群中的每一个节点,都是按照事务请求的顺序生效的。

  • Leader 为了保证提案按 ZXID 顺序生效,使用了一个 ConcurrentHashMap, 记录所有未提交的提案 ,命名为outstandingProposals

ZooKeeper 集群的写入是由 Leader 结点协调的,真实场景下写入会有一定的并发量,那 Zab 协议的两阶段提交是如何保证事务严格按顺序生效的呢?

  1. 每发起一个提案,会将提案的 ZXID 和内容放到 outstandingProposals 中,作为待提交的提案;

  2. 收到 Follower 的 ACK 信息后,根据 ACK 中的 ZXID 从 outstandingProposals 中找到对应的提案,对 ACK 计数;

  3. 执行 tryToCommit 尝试将提案提交,判断:

    1. 先判断当前 ZXID 之前是否还有未提交提案,如果有,当前提案暂时不能提交;
    2. 再判断提案是否收到半数以上 ACK,达到半数,则可以提交。将当前 ZXID 从 outstandingProposals 中清除并向 Followers 广播提交当前提案;

Leader 是如何判断当前 ZXID 之前是否还有未提交提案的呢?

由于前提是保证顺序提交的,所以 Leader 只需判断 outstandingProposals 里,当前 ZXID 的前一个 ZXID 是否存在。

简单来说,就是通过对比新提案的 ZXID 与自身最新 ZXID 是否相差1。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值