Kyle's Notebook

《DDIA》阅读笔记(九):一致性与共识(共识问题)

Word count: 7.9kReading time: 26 min
2021/02/24

《DDIA》阅读笔记(九):一致性与共识(共识问题)

本章结构如下:

  • 一致性与共识
    • 共识问题
      • 原子提交与两阶段提交:停止服务/人工介入
        • 事务原子提交
        • 两阶段提交(2PC)
        • 系统的承诺
        • 协调者发生故障
        • 三阶段提交(3PC)
      • 实践中的分布式事务
        • Exactly-Once 消息处理
        • XA 交易
        • 停顿时仍持有锁
        • 从协调者故障中恢复
        • 局限性
          • 单点故障
          • 有状态
          • 高复杂度
          • 低容错
      • 支持容错的共识:自动切换
        • 共识算法与全序广播
        • 主从复制与共识
        • Epoch 和 Quorum
        • 局限性
          • 同步复制
          • 故障误判
          • 多数节点
          • 静态成员组成
          • 网络问题敏感
    • 成员与协调服务
      • 节点任务分配
      • 服务发现
      • 成员服务

有很多重要的场景都需要集群节点达成某种一致:

  • 主节点选举:对于主从复制的数据库,所有节点需要就何者充当主节点达成一致。如果由于网络故障原因出现节点之间无法通信,就很容易出现争议。此时共识对于避免错误的故障切换非常重要,后者会导致脑裂:如果集群中存在两个主节点都在接受写请求,最终导致数据不一致甚至丢失。

  • 原子事务提交:对于支持跨节点或跨分区事务的数据库,可能存在某个事务在一些节点上执行成功,在其他节点执行失败的情况。为了维护事务的原子性,所有节点必须对事务的结果达成一致:成功提交或失败回滚。

共识的不可能性 — FLP 理论:如果节点存在可能崩溃的风险,则不存在总是能够达成共识的稳定算法。在分布式系统中必须假设节点可能会崩溃。

  • FLP 结论是基于异步系统模型而做的证明,假定确定性算法都不能使用任何时钟或超时机制(来检测崩溃节点)。

  • 另外即使算法使用了随机数来检测节点故障也可以绕过 FLP 结论。

无主复制和多主复制等系统通常不支持全局共识:这些系统可能会发生冲突,或许也可以接受或者寻找其他方案,例如没有线性化保证时就需要处理好数据多个冲突分支以及版本合并等。

共识意味着就某一项提议,所有节点做出一致的决定而且不可撤销。多个广泛的问题最终都可以归结为共识并且彼此等价,因此如果找到其中一个解决方案,就可以容易地将其转换为其他问题的解决方案。

  • 可线性化的“比较设置”寄存器:寄存器需要根据当前值是否等于输入的参数,来自动决定接下来是否应该设置新值。

  • 原子事务提交:数据库需要决定是否提交或中止分布式事务。

  • 全序广播:消息系统要决定以何种顺序发送消息。

  • 锁与租约:当多个客户端争抢锁或租约时,要决定其中哪一个成功。

  • 成员/协调服务:对于失败检测器(例如超时机制),系统要决定节点的存活状态(例如基于会话超时)。

  • 唯一性约束:当多个事务在相同的主键上试图并发创建冲突资源时, 约束条件要决定哪一个被允许,哪些违反约束因而必须失败。

原子提交与两阶段提交:停止服务/人工介入

当一个包含多笔写操作的事务在执行过程出现任何意外,原子性为上层应用提供简单的语义:事务结果成功提交或失败回滚。可以防止失败的事务破坏系统,避免形成部分成功夹杂着部分失败。这对于多对象事务和维护二级索引(与主数据保持一致)很重要。

事务原子提交

对于单数据库节点执行事务,原子性由存储引擎保证。当客户端请求数据库节点提交事务时,数据库首先使事务的写入持久化(如预写日志),然后把提交记录追加写入到磁盘的日志文件中。如果数据库在该过程中间发生了崩溃,当节点重启后事务可以从日志中恢复。如果在崩溃之前提交记录已成功写入磁盘,则认为事务已安全提交;否则回滚该事务的所有写入。

当事务涉及多个节点,比如一个分区数据库中多对象事务,或者基于词条分区的二级索引,向所有节点发送提交请求、各节点独立执行提交则容易造成部分失败:

  • 某些节点可能会检测到违反约束或有冲突而决定中止,而其他节点则可能成功提交。

  • 某些提交请求可能在网络中丢失,最终由于超时而中止,而其他提交请求则通过。

  • 某些节点可能在日志记录写入之前发生崩溃,然后在恢复时回滚,而其他节点则成功提交。

一部分节点提交了事务,而其他节点却放弃了事务,节点之间就会变得不一致而且某个节点一旦提交了事务,即使使事后发现其他节点发生中止,也无法再撤销已提交的事务。因此要求数据一旦提交,即被其他事务可见,进而其他客户端会基于此做出相应的决策。

可以通过补偿性事务达到一致,即试用一笔新的事务低效效果,但前后两笔事务完全独立,跨事务正确性由应用层保证。

两阶段提交(2PC)

一种在多节点之间实现事务原子提交的算法,也称作阻塞式原子提交协议。

在某些数据库内部使用,或者以 XA 事务形式或 SOAP Web 服务 WS-AtomicTransaction 形式提供给应用程序。

两阶段提交

2PC 引入了新组件 协调者(通常实现为共享库,运行在请求事务的同一进程中,或其他单独的进程或服务,比如 Narayana,JOTM,BTM,MSDTC)。通常 2PC 事务从应用程序在多个数据库节点(参与者)上执行数据读写开始,当应用程序准备提交事务时:

  • 协调者开始阶段 1:发送准备请求到所有节点,寻问是否可以提交。

  • 参与者回应是,则协调者在阶段 2 发出提交请求,提交开始实际执行。

  • 参与者回应否,则协调者在阶段 2 发出放弃请求。

系统的承诺

2PC 过程:

  • 应用程序启动分布式事务时,首先向协调者请求全局唯一的事务 id。

  • 应用程序在每个参与节点上执行单节点事务,将全局唯一事务 id 附加到事务上。此时读写在单节点完成,如果出现问题则协调者和其他参与者可以安全中止。

  • 当应用程序准备提交时,协调者向所有参与者发送准备请求,并附带全局事务 id。如果准备请求中的任意一个发生失败或超时,则协调者通知所有参与者放弃事务。

  • 参与者收到准备请求后,确保任何情况下都可以提交事务,包括安全地将事务数据写入磁盘,并检查是否存在冲突或约束违规。一旦向协调者回答“是”则承诺会提交事务。此时仍未真正提交,只是表态不会放弃事务。

  • 提交点:当协调者收到所有准备请求的答复时,就提交或放弃事务做出明确决定。协调者把最后的决定写入磁盘事务日志中,防止系统容崩溃,并可恢复之前的决定。

  • 协调者的决定写入磁盘之后,向所有参与者发送提交或放弃请求。如果出现失败或超时,则协调者必须一直重试直到成功,任何节点不允许反悔。即使参与者在此阶段故障,在恢复之后也必须继续执行。

协调者发生故障

如果参与者或网络在 2PC 期间发生失败,如果在第一阶段,则协调者会决定中止交易;如果在第二阶段,则协调者将无限期重试。

但对于协调者故障的情况:

  • 如果在发送准备请求前失败,则可以安全中止。

  • 如果参与者收到准备请求且做出“是”的回应,则参与者不能单方面放弃,必须等待协调者决定,此时参与者处于不确定状态(不能通过超时机制解决,可能导致参与者间状态不一致)。

  • 唯一方法是等待协调者恢复,因此协调者在向参与者发送提交或中止请求前要将决定写入磁盘事务日志,恢复后通过读取日志确定未决事务状态(没有日志则中止)。

三阶段提交(3PC)

3PC 假定一个有界的网络延迟和节点在规定时间内响应(现实情况很难确保,则无法保证原子性)。

通常非阻塞原子提交依赖于完美的故障检测器,可靠地判断节点是否已崩溃。但在无限延迟的网络环境中,超时机制不是可靠的故障检测器,因为即使节点正常,请求也可能由于网络问题而最终超时。

因此普遍还是使用阻塞的 2PC。

实践中的分布式事务

分布式事务一方面被看作式一个其他方案难以企及的重要安全保证,另一方面由于操作上的缺陷、性能问题(MySQL 的分布式事务比单节点事务慢 10 倍以上,2PC 的性能问题主要是来自防止崩溃恢复而做的磁盘 I/O,fsync,以及额外的网络往返开销)和承诺不可靠等问题遭受诟病,因此由于运维方面的问题很多云服务商决定不支持分布式事务。


两种分布式事务概念:

  • 数据库内分布式事务:某些分布式数据库(标配支持复制和分区)支持跨节点的内部事务,比如 VoltDB 和 MySQL CLuster 的 NDB 存储引擎。此外,所有参与节点都运行着相同的数据库软件。

  • 异构分布式事务:即存在两种或以上的参与者实现技术。比如来自不同供应商的数据库,或非数据库系统(消息中间件)。即使是完全不同的系统,跨系统的分布式事务也必须确保原子提交。

Exactly-Once 消息处理

异构分布式事务旨在无缝集成多种系统,结果是恰好一次地处理。

  • 比如数据库与消息队列,可通过自动提交消息确认和数据库写入来实现。如果消息发送或数据库事务任何一个发送失败,则两者必须中止,消息队列可稍后重传消息。因此自动提交消息和消息处理结果,可确保消息有效处理且仅有一次。而如果事务最后发生中止,则放弃所有部分完成结果。

  • 只有在所有受影响的系统都使用相同的原子提交协议的前提下,这种分布式事务才是可行的。如果不支持两阶段提交,在某个环节出错需要重试,则可能会重复发送。除非假定结果和副作用都可以在事务中止时回滚,则可以安全地重新处理消息。

XA 交易

X/Open XA(eXtended Architecture XA)是异构环境下实施 2PC 的工业标准,在许多关系型数据库(PostgreSQL、MySQL、DB2、SQL Server、Oracle)和消息队列(ActiveMQ、HornetQ、MSMQ、IBM MQ)都支持 XA。

  • XA 是一个与事务协调这通信的 C API,也支持其他语言的 API 绑定,比如 Java 的 JTA,可以支持很多 JDBC 驱动和 JMS 驱动。

  • XA 假定应用程序通过网络或客户端的库函数与参与者节点进行通信。如果驱动程序支持 XA, 意味着应用可以调用 XA API 确定操作是否是异构分布式事务的一部分,是则发送必要的信息给数据库服务器。同时它还支持回调,协调者通过回调函数通知参与者执行准备、提交或中止。

  • 事务协调者实现 XA API,与产生事务的应用程序运行在相同的进程中。这些 API 跟踪事务中所有的参与者,通过回调协调节点进行准备工作,然后收集参与者投票,在本地磁盘文件记录事务最终决定。

  • 如果应用程序进程发生崩溃或者所在的节点出现故障,协调者就需要做相应的处理,所有完成了准备阶段但尚未提交的参与者就会陷入停顿。由于事务日志保存在应用服务器本地磁盘上,该节点必须先重启,然后协调者通过 XA API 读取日志、进而恢复事务的决定。完成这些之后协调者才能继续使用数据库驱动 XA 回调来要求所有参与者执行提交(或中止)。数据库服务器无法直接与协调者进行通信,而须通过相应的 API。

停顿时仍持有锁

在分布式事务中需要关注陷入停顿、不确定应该提交或中止的参与者节点,不能忽略、清理而维持工作。

  • 数据库事务通常持有待修改行的行级独占锁,用以防止脏写。如果要使用可串行化隔离,2PC 锁的数据库还会对事务曾经读取的行持有读共享锁。

  • 在事务提交或中止前,数据库不会释放锁,事务在整个停顿期间都一直持有。如果协调者日志由于某种原因彻底丢失,数据对象将永久处于锁状态中,至少需要管理员手动解决。

  • 数据被加锁时其他事务无法执行修改甚至读取,导致上层应用不可用,因此必须解决处于停顿状态的事务。

从协调者故障中恢复

理论上如果协调者崩溃之后重新启动应该可以从日志中恢复那些停顿的事务,然而在实践中孤立的不确定事务确实会发生:

  • 当协调者恢复失败,基于 2PC 的正确实现要求,重启处于停顿状态的数据库节点也无法解决问题,因为要保持重启前事务的加锁。

  • 唯一的解决方法是让管理员手动决定究竟是执行提交还是回滚。管理员仔细检查每个有问题的参与者,确定是否有节点已完成提交(或中止),将相同的结果一一应用于所有的参与者上。此时需要大量的手工操作,由巨大的压力和时间限制。

  • 许多 XA 的实现都支持紧急避险措施,称之为启发式决策:参与者节点可以在紧急情况下单方面做出决定,放弃或者继续停顿事务,而不需要等到协调者发出指令。但启发式意味着破坏原子性,违背了 2PC 做出的承诺,只能用于应急。

局限性

XA 事务解决了多个参与者之间达成一致的问题,但同时引入了操作的限制,特别当核心的事务协调者本身就是数据库(存储事务投票结果)。

单点故障

如果协调者不支持数据复制,而是在单节点上运行,则存在单点故障问题(导致很多应用阻塞在停顿事务的锁上)。现实情况是有许多协调者的实现默认情况下并非高可用,或者只支持最基本的复制。

有状态

许多服务器端应用程序都倾向于无状态模式(HTTP),所有的持久状态都保存在数据库中,应用服务器可轻松地添加或删除实例。但当协调者是应用服务器的一部分时,协调者的日志成为可靠系统的重要组成部分,它要求与数据库本身一样重要(需要日志恢复有疑问的事务),此时应用服务器不再无状态。

高复杂度

由于 XA 需要与各种数据系统保持兼容,它最终是多系统可兼容的最低标准。比如它无法深入探测不同系统之间的死锁条件(需要更复杂的协议使得不同系统交换事务等待的锁信息),不适用于 SSI(需要更复杂的协议识别不同系统间的写冲突)。

低容错

数据库内部的分布式事务限制更少,比如支持 SSI(可串行化快照隔离)。但 2PC 要成功提交事务,还要求所有参与者都投票赞成,如果有任何部分故障,整个事务只能失败,意味着扩大事务失败的风险,降低容错性。

支持容错的共识:自动切换

共识即让几个节点就某项提议达成一致,共识问题即是一或多个节点可以提议某些值,由共识算法来决定最终值,其中共识算法必须满足:

  • 协商一致性(Uniform Agreement):所有节点接受相同的决议。

  • 诚实性(Integrity):所有节点不能反悔,对一项决议不能有两次决定。与协商一致性定义了共识的核心,即决定一致的结果不能改变。

  • 合法性(Validity):如果决定了值 v,则 v 一定是由某个节点所提议的。比如该决定不能为 NULL,即使满足协商一致性和诚实性,也没有任何实际效果。

  • 可中止性(Termination):节点如果不崩溃则最终一定可以达成决议。当考虑容错性,就不能强行指定某个节点独裁、做出所有决定。可中止性点强调一个共识算法不能原地空转,必须取得实质性进展。

上述共识的系统模型假定在满足某种限制下,当某个节点发生崩溃后(小于半数节点),节点就彻底消失不再出现。

在该模型下,所有采取等待节点恢复的算法都无法满足终止性,特别是 2PC 不符合可终止性要求。

大多数共识算法都假定系统不存在拜占庭式错误,而对于拜占庭式错误,只要故障节点数小于三分之一也可以达成共识。

共识算法与全序广播

著名的容错式共识算法包括 VSR、Paxos、Raft、Zab,其中大部分不是直接使用共识的形式化模型(提议并决定某个值,同时满足四个属性),而是决定一系列的值,采用全序关系广播算法,其要点是消息按照相同的顺序有且只有一次地发送到所有节点,相当于进行了多轮共识:在每一轮节点提出要发送的消息、决定下一个消息全局顺序。

  • 协商一致性:所有节点决定相同的顺序发送相同消息。

  • 诚实性:消息不能重复。

  • 合法性:消息不会被破坏,也不能凭空捏造。

  • 可中止性:消息不会丢失。

VSR、Raft、Zab 都直接采取全序关系广播,比重复性的一轮共识,只解决一个提议更高效,而 Paxos 则有对应的优化版本 Multi-Paxos。

主从复制与共识

在主从复制中所有写入操作由主节点负责,并以相同的顺序发送到从节点来保持副本更新。其是否全序关系广播,取决于如何选择主节点。

  • 如果主节点由管理员手动选择配置,即为独裁式的一致性算法:只允许一个节点接受写入,如果发生故障则系统无法写入,直到手动配置新节点为主节点。这种做法需要认为干预,不满足共识的可终止性。

  • 一些数据库支持持自动选举主节点和故障切换,通过选举把某个从节点者提升为新的主节点,更接近容错式全序关系广播,从而达成共识。

  • 脑裂问题:所有的节点都需要同意主节点,否则两个主节点会导致数据库出现不一致,因此需要共识算法选出一位主节点。但是如果这里描述的共识算法实际上是全序关系广播,且全序关系广播很像主从复制,主从复制又需要选举主节点,则形成循环。

Epoch 和 Quorum

目前讨论的共识协议在内部都使用某种形式的主节点,都采用了一种弱化的保证:协议定义了一个世代编号(epoch number),如 Paxos 中的 ballot number、VSP 中的 view number、Raft 中的 term number,并保证在每个世代中主节点都是唯一确定的。

  • 如果发现当前的主节点失效,节点就开始一轮投票选举新的主节点,选举会赋予一个单调递增的 epoch 号。如果出现了两个不同的主节点对应千不同 epoch 号,则更高 epoch 号的主节点获胜。

  • 在主节点做出任何决定之前须检查是否存在比它更高的 epoch 号码,否则就会产生冲突的决定。节点不能依靠自己所掌握的信息来决策,不能仅仅自认为主节点。

  • 节点须从 quorum 节点中收集投票。主节点如果想要做出某个决定,须将提议发送给其他所有节点,等待 quorum 节点的响应。quorum 通常由多数节点组成,且只有当没有更高 epoch 的主节点存在时,节点才会对当前的提议进行投票。

  • 两轮投票:首先是投票决定谁是主节点,然后是对主节点的提议进行投票。且参与两轮的 quorum 必须有重叠(至少有一个参与者参与了两次投票)。如果在针对提议的投票中没有出现更高 epoch 号码,就可以得出结论:没有发生更高 epoch 的主节点选举, 当前的主节点地位没有改变,可以安全地就提议进行投票。

区别于 2PC,共识算法同时具备正确性和容错性:

  • 2PC 的协调者并不是依靠选举产生。

  • 容错共识算法只需要收到多数节点的投票结果即可通过决议,而 2PC 要求每个参与者都必须做出“是”才能最终通过。

  • 共识算法定义了恢复过程,出现故障后通过该过程节点可以选举出新的主节点,然后进入一致的状态,确保总是能够满足安全属性。

局限性

共识算法兼顾安全性(一致性、完整性、有效性)和容错性(大多数节点工作就能保证可用)。共识可提供全序关系广播,以容错的方式实现线性化。

但也存在以下代价需要考量。

同步复制

在达成一致性决议之前,节点投票的过程是同步复制过程,这意味着牺牲性能。

因此数据库通常配置为异步复制,即使存在某些已提交数据在故障切换时丢失数据的情况。

故障误判

共识系统通常依靠超时机制检测节点失效。在网络延迟高度不确定的环境中,特别是跨区域分布的系统,经常由于网络延迟的原因,导致节点错误地认为主节点发生故障。

这种误判并不会损害安全属性,但频繁的主节点选举显著降低了性能,系统最终会花费更多的时间和资源在选举主节点上而不是原本的服务任务。

多数节点

共识体系需要严格的多数节点才能运行,如果由于网络故障切断了节点之间的连接,则只有多数节点所在的分区可以继续工作,剩下的少数节点则处于事实上的停顿状态。

(线性化的代价)

静态成员组成

多数共识算法假定一组固定参与投票的节点集, 这意味着不能动态添加或删除节点。

动态成员资格的扩展特性可以在集群中的按需调整节点数,但相比于静态的成员组成,其理解程度和接受程度要低很多。

网络问题敏感

共识算法往往对网络问题特别敏感。

例如 Raft 已被发现存在不合理的边界条件处理,如果网络中存在某一条网络连接持续不可靠,Raft 就会不断在两个节点之间反复切换主节点,当前主节点不断被赶下台,这最终导致系统根本无法正常提供服务。

其他共识算法也有类似的问题,所以面对不可靠网络,如何设计更具鲁棒性的共识算法仍然是一个开放性的研究问题。

成员与协调服务

ZooKeeper 和 Etcd 通常称为“分布式键值存储”或“协调与配置服务”,其对外提供读写入主键的值,或遍历主键等 API。

但区别于主流数据库,其主要针对保存少量、可完全载入内存的数据(即使可以持久化)。通常采用容错的全序广播算法在所有节点上复制这些数据从而实现高可靠(每条消息代表数据库写请求,按相同的顺序在多个节点应用写操作,从而达到多副本的一致性)。

ZooKeeper 模仿了 Google Chubby 分布式锁服务,并提供了很多其他特性:

  • 线性化原子操作:使用原子“比较-设置”操作, 可以实现加锁服务。

  • 操作全序:当资源被锁或者租约保护时, 需要 fencing 令牌来防止某些客户端发生进程暂停而引起锁冲突。

  • 故障检测:客户端与 ZK 节点维护一个长期会话 客户端会周期性地与 ZK 服务节点互相交换心跳信息,以检查对方是否存活(可识别闪断或长时间心跳停止,自动处理锁资源释放)。

  • 更改通知:客户端不仅可以读取其他客户端所创建的锁和键值,还可以监视它们的变化,即订阅通知机制。

节点任务分配

  • 如果系统有多个流程或服务的实例,并且需求其中的一个实例充当主节点;而如果主节点失效,由其他某个节点来接管。这非常吻合主从复制数据库,此外它对于作业调度系统(或类似的有状态服务)也非常有用。

  • 对于一些分区资源(数据库、消息流、文件存储、分布式 Actor System),需要决定将那个分区分配给哪个节点,当有新节点加入集群时,需要将某些现有分区从当前节点迁移到新节点从而实现负载动态平衡,则可以利用 ZK 的原子操作 ephemeral nodes 和通知机制实现。如果实现无误,它可以非常方便地使应用程序达到自动故障中恢复,减少入工干预。

  • 应用程序最初可能只运行在单节点,之后可能最终扩展到数千节点。试图在如此庞大的集群上进行多数者投票会非常低效。ZK 通常是在固定数最的节点(通常三到五个)上运行投票,可以非常高效地支持大量的客户端。因此 ZK 其实提供了一种将跨节点协调服务(包括共识,操作排序和故障检测)专业外包的方式。

  • ZK 管理的数据一般变化非常缓慢(分钟、小时级),如果需要保存实时变化的数据,应该考虑其他工具(比如 Apache BookKeeper)。

服务发现

即确定需要某项服务时,应该连接到哪个 IP 地址等。使用 ZK,可以在节点启动时将网络端口信息向 ZK 等服务注册,客户端只需要向 ZK 注册表寻问。

  • 服务发现不一定需要共识。通过服务名称来获取 IP 地址传统的查询方式是 DNS,它使用多层缓存来实现高性能与高可用性。从 DNS 读取肯定不满足线性化,而现实情况是如果 DNS 返回的结果是过期的旧值通常也不会产生什么大问题。DNS 对于网络产生中断时服务可用性和鲁棒性更为重要一些。

  • 即使服务发现不需要共识,但主节点选举肯定需要。因此如果共识系统已经明确知道主节点,可以利用来帮助次级服务发现各自的主节点。现在一些共识系统支持只读的缓存副本。这些副本异步地接收其他共识算法所达成的决议日志,但自身并不太参与投票,而主要是提供不需要支持线性化的读取服务。

成员服务

用来确定当前哪些节点处于活动状态并属于集群的有效成员。由于无限的网络延迟无法可靠地检测一个节点究竟是否发生了故障。但是可以将故障检测与共识绑定,让所有节点就节点的存活达成一致意见。

虽然依然可能误判,即便如此系统就成员资格问题的决定是全体一致是最重要的。例如选举主节点的方式可能是简单地投票选择编号最小的节点,一且节点对于当前包含哪些成员出现了不同意见,共识过程就无法继续。

CATALOG
  1. 1. 《DDIA》阅读笔记(九):一致性与共识(共识问题)
    1. 1.1. 原子提交与两阶段提交:停止服务/人工介入
      1. 1.1.1. 事务原子提交
      2. 1.1.2. 两阶段提交(2PC)
      3. 1.1.3. 系统的承诺
      4. 1.1.4. 协调者发生故障
      5. 1.1.5. 三阶段提交(3PC)
    2. 1.2. 实践中的分布式事务
      1. 1.2.1. Exactly-Once 消息处理
      2. 1.2.2. XA 交易
      3. 1.2.3. 停顿时仍持有锁
      4. 1.2.4. 从协调者故障中恢复
      5. 1.2.5. 局限性
        1. 1.2.5.1. 单点故障
        2. 1.2.5.2. 有状态
        3. 1.2.5.3. 高复杂度
        4. 1.2.5.4. 低容错
    3. 1.3. 支持容错的共识:自动切换
      1. 1.3.1. 共识算法与全序广播
      2. 1.3.2. 主从复制与共识
      3. 1.3.3. Epoch 和 Quorum
      4. 1.3.4. 局限性
        1. 1.3.4.1. 同步复制
        2. 1.3.4.2. 故障误判
        3. 1.3.4.3. 多数节点
        4. 1.3.4.4. 静态成员组成
        5. 1.3.4.5. 网络问题敏感
    4. 1.4. 成员与协调服务
      1. 1.4.1. 节点任务分配
      2. 1.4.2. 服务发现
      3. 1.4.3. 成员服务