Zookeeper一致性协议

余生长醉 提交于 2020-12-14 22:17:24

1、前言

随着PC机性能的不断提升和网络技术的快速普及,很多企业开始放弃原来的大型主机,而改用小型机和普通PC服务器来搭建分布式的计算机系统。其中最为典型的就是阿里巴巴集团的 "去 IOE" 运动。

在以前集中式的应用,我们很容易的能够实现一套满足ACID特性的事务处理系统,来保证数据的严格一致性。但在分布式的应用中,数据分散在各台不同的机器上,要想保证数据的严格一致性就很难了。因此出现了CAP和BASE这样的分布式系统经典理论。

1.1、ACID

事务(Transaction)是由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元(Unit),狭义上的事务特指数据库事务。

事务包含四大特性,分别是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

1.1.1、原子性

原子性是指事务必须是一个原子的操作序列单元。每一个事务的所有操作要么全部成功,要么全部失败。

1.1.2、一致性

一致性是指事务的执行不能破环数据库数据的完整性和一致性。一个事务在执行之前和执行之后,数据库都必须处于一致性状态。

1.1.3、隔离性

隔离性是指并发的事务是相互隔离的,一个事务的执行不能被其他事务干扰。

1.1.4、持久性

持久性是指一个事务一旦提交,它对数据库中对应数据的状态变更就应该是永久性的。

1.2、CAP定理

CAP定理是指一个分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容错性(P:Partition tolerance)这三个基本需求,最多只能同时满足其中的两项。因为分布式系统中分区容错性是一定存在的,所以主要还是在一致性和可用性中进行权衡选择。

1.2.1、一致性

在分布式环境中,一致性是指数据在多个副本之间是否能够保持一致的特性。在一致性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态。

对于一个将数据副本分布在不同分布式节点上的系统来说,如果对第一个节点的数据进行了更新操作并且更新成功后,却没有使得第二个节点上的数据得到相应的更新,于是在对第二个节点的数据进行读取操作时,获取的依然是老数据(或称为脏数据),这就是典型的分布式数据不一致情况。在分布式系统中,如果能够做到针对一个数据项的更新操作执行成功后,所有的用户都可以读取到其最新的值,那么这样的系统就被认为具有强一致性(或严格的一致性)。

1.2.2、可用性

可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。

1.2.3、分区容错性

分区容错性约束了一个分布式系统需要具有如下特性:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。

网络分区是指在分布式系统中,不同的节点分布在不同的子网络(机房或异地网络等)中,由于一些特殊的原因导致这些子网络之间出现网络不连通的状况,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干个孤立的区域。需要注意的是,组成一个分布式系统的每个节点的加入与退出都可以看作是一个特殊的网络分区。

1.3、BASE理论

BASE理论是指 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的简写。是对 CAP 中一致性和可用性权衡的结果。是基于 CAP 定理逐步演化而来的,其核心思想是即使无法做到强一致性,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

1.3.1、基本可用

基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性——但请注意,这绝不等价于系统不可用。

1.3.2、弱状态

弱状态也称为软状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。

1.3.3、最终一致性

最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

2、一致性协议和算法

为了解决分布式一致性问题,在长期的探索研究过程中,涌现出了一大批经典的一致性协议和算法,其中最著名的就是二阶段提交协议、三阶段提交协议和Paxos算法。

2.1、2PC协议

2PC,是 Two-Phase Commit 的缩写,即二阶段提交,是计算机网络尤其是在数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务处理过程中能够保持原子性和一致性而设计的一种算法。

2.1.1、协议说明

二阶段提交协议是将事务的提交过程分成了两个阶段来进行处理。在讲述流程之前先介绍两个概念:

  • 协调者: 用来统一调度所有分布式节点的执行逻辑。

  • 参与者: 被调度的分布式节点。

其执行流程如下:

  • 阶段一:提交事务请求(投票阶段)

    1. 分发事务

      协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应。

    2. 执行事务

      各参与者节点执行事务操作,并将Undo 和 Redo 信息记入事务日志中。

    3. 反馈响应

      如果参与者成功执行了事务操作,那么就反馈给协调者 Yes 响应,表示事务可以执行;

      如果参与者没有成功执行事务,那么就反馈给协调者 No 响应,表示事务不可以执行。

  • 阶段二:执行事务请求(执行阶段)

    协调者根据各参与者的反馈情况来决定最终是否可以进行事务提交操作,正常情况下,包含以下两种可能。

    提交事务

    ​ 假如协调者从所有的参与者获得的反馈都是 Yes 响应,那么就会执行事务提交。

    1. 发送提交请求

      协调者向所有参与者节点发出 Commit 请求。

    2. 事务提交

      参与者接收到 Commit 请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源。

    3. 反馈事务提交结果

      参与者在完成事务提交之后,向协调者发送Ack消息。

    4. 完成事务

      协调者接收到所有参与者反馈的 Ack 消息后,完成事务。

    中断事务

    ​ 假如任何一个参与者向协调者反馈了 No 响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。

    1. 发送回滚请求

      协调者向所有参与者节点发出 Rollback 请求。

    2. 事务回滚

      参与者接收到 Rollback 请求后,会利用其在阶段一中记录的 Undo 信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。

    3. 反馈事务回滚结果

      参与者在完成事务回滚之后,向协调者发送 Ack 消息。

    4. 中断事务

      协调者接收到所有参与者反馈的 Ack 消息后,完成事务中断。

2.1.2、优缺点

优点:

  • 原理简单
  • 实现方便

缺点:

  • 同步阻塞

    二阶段提交协议存在的最明显也是最大的一个问题就是同步阻塞,这会极大地限制分布式系统的性能。在二阶段提交的执行过程中,所有参与该事务操作的逻辑都处于阻塞状态,也就是说,各个参与者在等待其他参与者响应的过程中,将无法进行其他任何操作。

  • 单点问题

    在二阶段提交中,一旦协调者出现问题,那么整个二阶段提交流程将无法运转,更为严重的是,如果协调者是在阶段二中出现问题的话,那么其他参与者将会一直处于锁定事务资源的状态中,而无法继续完成事务操作。

  • 数据不一致

    在二阶段提交协议的阶段二,即执行事务提交的时候,当协调者向所有的参与者发送Commit 请求之后,发生了局部网络异常或者是协调者在尚未发送完 Commit 请求之前自身发生了崩溃,导致最终只有部分参与者收到了 Commit 请求。于是,这部分收到了 Commit 请求的参与者就会进行事务的提交,而其他没有收到 Commit 请求的参与者则无法进行事务提交,于是整个分布式系统便出现了数据不一致性现象。

  • 太过保守

    二阶段提交协议没有设计较为完善的容错机制,任意一个节点的失败都会导致整个事务的失败。

2.2、3PC协议

2.2.1、协议说明

3PC,是 Three-Phase Commit 的缩写,即三阶段提交,是 2PC 的改进版,其将二阶段提交协议的“提交事务请求”过程一分为二,形成了由 CanCommit、PreCommit 和 DoCommit 三个阶段组成的事务处理协议。其执行流程如下:

  • 阶段一:CanCommit

    1. 事务询问

      协调者向所有的参与者发送一个包含事务内容的 CanCommit 请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应。

    2. 反馈响应

      参与者在接收到来自协调者的 CanCommit 请求后,正常情况下,如果其自身认为可以顺利执行事务,那么会反馈 Yes 响应,并进入预备状态,否则反馈 No 响应。

  • 阶段二:PreCommit

    协调者会根据各参与者的反馈情况来决定是否可以进行事务的 PreCommit 操作,正常情况下,包含两种可能。

    执行事务预提交

    ​ 假如协调者从所有的参与者获得的反馈都是 Yes 响应,那么就会执行事务预提交。

    1. 发送预提交请求

      协调者向所有参与者节点发出 PreCommit 的请求,并进入 Prepared 阶段。

    2. 事务预提交

      参与者接收到 PreCommit 请求后,会执行事务操作,并将 Undo 和 Redo 信息记录到事务日志中。

    3. 反馈响应

      如果参与者成功执行了事务操作,那么就会反馈给协调者 Ack 响应,同时等待最终的指令:提交 (commit)或中止(abort)。

    中断事务

    ​ 假如任何一个参与者向协调者反馈了No 响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。

    1. 发送中断请求

      协调者向所有参与者节点发出 abort 请求。

    2. 中断事务

      无论是收到来自协调者的 abort 请求,或者是在等待协调者请求过程中出现超时,参与者都会中断事务。

  • 阶段三:DoCommit

    该阶段将进行真正的事务提交,会存在以下两种可能的情况。

    执行提交

    1. 发送提交请求

      进入这一阶段,假设协调者处于正常工作状态,并且它接收到了来自所有参与者的 Ack 响应,那么它将从 “预提交” 状态转换到 “提交” 状态,并向所有的参与者发送 doCommit 请求。

    2. 事务提交

      参与者接收到 doCommit 请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源。

    3. 反馈事务提交结果

      参与者在完成事务提交之后,向协调者发送 Ack 消息。

    4. 完成事务

      协调者接收到所有参与者反馈的 Ack 消息后,完成事务。

    中断事务

    ​ 进入这一阶段,假设协调者处于正常工作状态,并且有任意一个参与者向协调者反馈了No 响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。

    1. 发送中断请求

      协调者向所有的参与者节点发送 abort 请求。

    2. 事务回滚

      参与者接收到 abort 请求后,会利用其在阶段二中记录的 Undo 信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。

    3. 反馈事务回滚结果

      参与者在完成事务回滚之后,向协调者发送 Ack 消息。

    4. 中断事务

      协调者接收到所有参与者反馈的 Ack 消息后,中断事务。

    注意:阶段三可能会存在以下两种故障

    • 协调者出现问题。
    • 协调者和参与者之间的网络出现故障。

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

2.2.2、优缺点

优点:

  • 相较于二阶段提交,三阶段提交降低了参与者的阻塞范围。
  • 能够在出现单点故障后继续达成一致。

缺点:

  • 数据不一致

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

2.3、Paxos算法

Paxos 算法是一种基于消息传递且具有高度容错特性的一致性算法。是目前公认的解决分布式一致性问题最有效的算法之一。(这里不介绍Paxos算法,后面会写一篇文章专题讲讲Paxos算法)

3、ZAB(Zookeeper Atomic Broadcast)协议

ZAB协议是为分布式协调服务Zookeeper专门设计的一种支持崩溃恢复的原子广播协议。基于该协议,Zookeeper实现了一种主备模式的系统架构来保持集群中各副本之间数据的一致性。

3.1、核心处理过程

所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为 Leader 服务器,而余下的其他服务器则成为 Follower 服务器。 Leader 服务器负责将一个客户端事务请求转换成一个事务 Proposal(提议),并将该 Proposal 分发给集群中所有的 Follower 服务器。之后 Leader 服务器需要等待所有 Follower 服务器的反馈,一旦超过半数的 Follower 服务器进行了正确的反馈后,那么 Leader 就会再次向所有的 Follower 服务器分发 Commit 消息,要求其将前一个 Proposal 进行提交。

3.2、协议介绍

ZAB协议包括两种基本的模式,分别是崩溃恢复和消息广播。当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断,崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步(数据同步)之后,ZAB 协议就会退出恢复模式,进入消息广播模式。Leader 服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播协议;而如果集群中的其他机器接收到客户端的写事务请求,那么这些非 Leader 服务器会首先将这个事务请求转发给 Leader 服务器。 如果新加入了一台服务器,此服务器就会进入数据恢复模式,找到Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。

3.2.1、崩溃恢复模式

当整个服务器框架启动过程中,或是当 Leader 服务器出现崩溃,或者说由于网络原因导致 Leader 服务器失去了与过半 Follower 的联系,那么就会进入崩溃恢复模式。(说白了崩溃恢复模式就是选举新的 Leader,完成数据同步)

在崩溃恢复过程中可能会出现两个数据不一致性的问题:

  • ZAB 协议需要确保那些已经在 Leader服务器上提交的事务最终被所有服务器都提交。

    假设一个事务在 Leader 服务器上被提交了,并且已经得到过半的 Follower 服务器的 Ack 反馈,但是在它将 Commit 消息发送给所有 Follower 机器之前,Leader 服务器挂了。针对这种情况, ZAB 协议就需要确保事务最终能够在所有的服务器上都被提交成功,否则将出现不一致。

  • ZAB 协议需要确保丢弃那些只在 Leader 服务器上被提出的事务

    假设在 Leader 服务器上 Server1 提出了一个事务之后就崩溃退出了,从而导致集群中的其他服务器都没有收到这个事务。于是,当 Server1 恢复过来再次加入到集群中的时候,ZAB 协议需要确保丢弃这个事务。

对于上面提出的问题,决定了 ZAB 协议必须设计这样一个 Leader 选举算法:能够确保提交已经被 Leader 提交的事务 Proposal,同时丢弃已经被跳过的事务 Proposal。针对这个要求,如果让 Leader 选举算法能够保证新选举出来的 Leader 服务器拥有集群中所有机器最高编号(即 ZXID 最大)的事务 Proposal,那么就可以保证这个新选举出来的 Leader 一定具有所有已经提交的提案。更为重要的是如果让具有最高编号事务 Proposal 的机器来成为 Leader ,就可以省去 Leader 服务器检查 Proposal 的提交和丢弃工作的这一步操作了。(Leader选举过程请看 《Zookeeper深入原理》)

在完成 Leader 选举之后,正式开始工作之前,还需要确认事务日志中的所有 Proposal 是否都已经被集群中过半的机器提交了,即是否完成了数据同步。

下面介绍一下数据同步过程:

Leader服务器会为每一个 Follower服务器都准备一个队列,并将那些没有被各 Follower 服务器同步的事务以 Proposal 消息的形式逐个发送给 Follower 服务器,并在每一个 Proposal 消息后面紧接着再发送一个 Commit 消息,以表示该事务已经被提交。等到 Follower 服务器将所有其尚未同步的事务 Proposal 都从 Leader 服务器上同步过来并成功应用到本地数据库中后,Leader 服务器就会将该 Follower 服务器加入到真正的可用 Follower 列表中。

3.2.2、消息广播模式

ZAB 协议的消息广播过程使用的是一个原子广播协议,类似于一个二阶段提交的过程。但是 ZAB 协议与二阶段提交略有不同。在 ZAB 协议的二阶段提交过程中,移除了中断逻辑,所有的 Follower 服务器要么正常反馈 Leader 提出的事务 Proposal,要么就抛弃 Leader 服务器。同时 ZAB 协议支持半数原则,即超过半数的 Follower 服务器反馈 Ack 之后就开始提交事务 Proposal 了,而不需要等待集群中所有的 Follower 服务器都反馈响应。

因为这种简化的二阶段提交模型下,是无法处理 Leader 服务器崩溃退出而带来的数据不一致问题的,因此在ZAB 协议中添加了崩溃恢复模式来解决这种问题。

在消息广播过程中,Leader 服务器会为每一个事务请求生成对应的 Proposal 来进行广播,并且在广播事务 Proposal 之前,Leader 服务器会首先为这个事务 Proposal 分配一个全局单调递增的唯一ID(即 ZXID),Leader服务器会为每一个 Follower 服务器都各自分配一个单独的队列,然后将需要广播的事务 Proposal 依次放入这些队列中去,并且根据 FIFO 策略进行消息发送。每一个 Follower 服务器在接收到这个事务 Proposal 之后,都会首先将其以事务日志的形式写入到本地磁盘中去,并且在成功写入后反馈给 Leader 服务器一个 Ack 响应。当 Leader 服务器接收到超过半数 Follower 的 Ack 响应后,就会广播一个 Commit 消息给所有的 Follower 服务器以通知其进行事务提交,同时 Leader 自身也会完成对事务的提交,而每一个 Follower 服务器在接收到 Commit 消息后,也会完成对事务的提交。

3.3、协议说明

整个 ZAB 协议主要包括消息广播和崩溃恢复两个过程,进一步可以细分为三个阶段,分别是发现(Discovery)、同步(Synchronization)和广播(Broadcast)阶段。

阶段一:发现

主要就是 Leader 选举过程,用于在多个分布式进程中选举出主进程。

阶段二:同步

在完成发现流程之后,就进入了同步阶段。即 Leader 服务器和 Follower 服务器之间同步数据。

阶段三:广播

完成同步阶段之后,ZAB 协议就可以正式开始接收客户端新的事物请求,并进行消息广播流程。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!