分布式系统-VM-FT阅读笔记

什么是 two-phase commit(两阶段提交)

两阶段提交是一种原子提交协议,用于确保分布式事务的原子性(要么全部执行,要么全部不执行)。它主要包含两个阶段:

  1. 准备阶段(Prepare Phase):事务协调者向所有参与者发送 Prepare 消息,询问是否可以提交事务。参与者需要将事务数据持久化到磁盘并锁定相关资源,然后回复 Yes 或 No。
  2. 提交阶段(Commit Phase):如果所有参与者都回复 Yes,协调者发送 Commit 消息;如果有任何参与者回复 No 或超时,则发送 Abort 消息。

关键特性

  • 所有参与者必须就提交或中止达成一致。
  • 需要等待所有参与者的响应。
  • 在故障情况下可能导致阻塞(Block)。

为什么两阶段提交能让备份虚拟机判断崩溃发生时机?

Without the use of transactions with two-phase commit when the primary intends to send an output, there is no way that the backup can determine if a primary crashed immediately before or after sending its last output.

概念解释

两阶段提交通过明确的协议阶段划分,为备份虚拟机提供了判断崩溃时机的关键信息:

准备阶段(prepare phase):主虚拟机在发送输出前会先进入准备阶段,向备份虚拟机发送准备消息并等待确认。此时:

  • 如果主虚拟机在发送准备消息前崩溃,备份虚拟机不会收到任何通知
  • 如果主虚拟机在发送准备消息后崩溃,备份虚拟机会记录"已准备"状态

提交阶段(commit phase):只有当备份虚拟机确认准备完成后,主虚拟机才会实际发送输出。此时:

  • 如果主虚拟机在发送输出前崩溃,备份虚拟机知道处于"已准备但未提交"状态
  • 如果主虚拟机在发送输出后崩溃,备份虚拟机会收到提交确认

关键点在于两阶段提交创建了明确的"准备点",使得崩溃时机可以被准确定位到三个阶段之一:准备前、准备后提交前、提交后。

示例说明

以网络数据包发送为例:

1
2
3
4
5
主虚拟机状态        备份虚拟机状态
1. 准备发送数据包 --> 记录"准备发送数据包X"
   (等待确认)
2. 收到确认
3. 实际发送数据包 --> 记录"已发送数据包X"

如何应对脑裂?

首先,如何导致脑裂问题:当主备虚拟机因网络分区(如心跳丢失)同时认为自己是主节点时,会导致数据损坏或服务冲突。为此,vm-ft 使用共享存储(存储虚拟磁盘)作为仲裁机制。主备虚拟机通过原子性 test-and-set 操作竞争“锁”:成功者晋升为主节点,失败者终止(“自杀”)。

设 S1 为主节点,S2 为备节点。当网络断开后,S2 检测到与 S1 失联并通过测试设置锁成功接管。S1 会知道 S2 已接管吗?是否会出现"脑裂"?

S1 可能确实不知情,客户端可能继续向其发送请求。但当 S1 尝试产生输出(给客户端或共享磁盘)时,输出规则要求其等待 S2 确认所有未完成日志条目。由于 S2 已不再是备节点(即使能通信)不会对 S1 发送确认,因此 S1 将永远等待而无法产生任何输出。所以 S1 自认为仍是主节点并不会实际导致脑裂。这种"无法获得备节点确认就停止服务"的模式常见于复制系统,是预防脑裂的关键机制。

如何解决日志系统阻塞问题?

vm-ft 的实现需要备用虚拟机完全执行日志系统传输的所有步骤,因此当消耗缓慢时主虚拟机会因为无法发送新的日志而阻塞。为此,vm-ft 首先要求备份虚拟机和主虚拟机的性能差距不大,同时在日志系统中附带额外信息检测执行延迟,当执行延迟过大时会缓慢限制主虚拟机的性能,直到备用虚拟机追赶上来。


受 VM-ft 启发,我们如何设计一个主/副备份的系统

首先,我们需要明确设计一个主/副备份系统需要面对哪些问题:

  1. 主节点和副节点之间状态的同步。
  2. 如何得知发生了故障,或者说何时切换至备用节点。
  3. 发生故障时,如何尽快切换至备用节点。
  4. 切换时,如何不丢失已对外输出的状态。
  5. 如何产生一个新的备份。

主节点和副节点之间状态的同步

VM-FT 通过确定性重放日志通道两个机制共同实现状态同步。

主节点会在运行过程中不断产生日志条目,副节点需要根据日志完全复刻主节点的操作,具体来说日志系统包含:

  1. 所有非确定性输入事件(如网络数据包、磁盘 I/O 请求等),确保备份虚拟机拥有与主虚拟机相同的外部输入。
  2. 虚拟机状态变更的必要信息,如中断、I/O 操作等,用于保证两个虚拟机执行路径的一致性。
  3. 输出规则(Output Rule)相关的控制信息,确保在故障切换时,备份虚拟机的输出与主虚拟机保持一致。

一旦副节点的 VMM 接受到了日志(不要求副节点执行完成),就会返回 ACK 告知主节点。对于输出信号,主节点等待副节点也准备输出后才能实际发送输出信号。但课程上也承认,在切换节点时难以完全避免重复的输出,幸运的是大部分基础设施可以应对重复数据。

如何得知发生了故障,或者说何时切换至备用节点

通常来说,主/副节点通过心跳机制确认对方的存在。超时发生后,副节点会尝试成为主节点,当然前文已经描述了如何避免脑裂。

发生故障时,如何尽快切换至备用节点

论文认为切换时延包含两部分:察觉发生故障的时间、进行切换的时间。为了减小察觉发生故障的时间,主副虚拟机之间的心跳检查非常频繁,一秒内将进行大量检查,这样能迅速反应。同时,为了减小切换时间,日志系统也十分关键:

确定性重放(Deterministic Replay)

备份虚拟机持续接收主虚拟机的日志条目并实时重放,保持其执行路径与主虚拟机一致。这使得在故障发生时,备份虚拟机可以立即接管执行,无需从头恢复状态。

低日志带宽需求

日志条目仅记录非确定性事件(如中断、I/O 请求),因此日志数据量很小,通常低于 20 Mbit/s。这种低带宽需求使得日志可以快速传输,从而减少备份虚拟机的滞后。

日志压缩机制

日志流具有高度可压缩性,通过简单的压缩技术可进一步减少带宽占用,加快日志传输速度,降低备份虚拟机同步延迟。

CPU 资源动态调整机制

如果备份虚拟机开始落后于主虚拟机,系统会通过缓慢降低主虚拟机的 CPU 资源配额,来“拖慢”主虚拟机,从而让备份虚拟机追上进度。这一机制确保备份虚拟机始终处于可接管状态。

快速故障检测机制

主备虚拟机之间通过心跳信号和日志确认(ACK)机制实时检测对方状态。一旦检测到主虚拟机故障,备份虚拟机即可立即接管,无需等待额外的恢复过程。

自动冗余恢复机制

故障切换后,VMware vSphere 的集群服务会自动选择一个合适的主机,通过 FT VMotion 创建新的备份虚拟机,以快速恢复容错状态。这时,应该是直接进行内存的完整拷贝以创建一个新的副本。

切换时,如何不丢失已对外输出的状态

首先,确认重放和日志系统已经确保了副节点会完全复刻主节点的执行过程,也就是说副节点也会完全复刻主节点的输出。那么问题将变为如何得知主节点是否已经将数据发送出去了。为此,系统采用的了两阶段提交的思想,也就是延迟实际输出

  • 主虚拟机在执行输出操作(如发送网络数据包)前,先记录一条输出日志条目并发送给备份虚拟机。
  • 主虚拟机会延迟实际输出,直到收到备份虚拟机对该日志条目的确认(ACK)。
  • 只有在确认后,主虚拟机才会真正执行对外输出。

那么,我们思考主节点在不同时间崩溃的情况:

  1. 准备阶段前:主节点在接到客户端请求或发出准备信号之前就崩溃,那么系统确实无法响应请求,但不会造成状态不一致。
  2. 准备阶段后:主节点发出了准备信号,接着崩溃。副节点正常准备数据,并返回确认。接着超时,副节点尝试成为主节点并继续流程,向客户端发送数据。
  3. 提交信号发出之前:主节点发送了数据,然后在发送提交信号前崩溃了。副节点确实会重复发送数据。这对网络和磁盘 I/O 不是问题:网络包的重复会被客户端 TCP 协议自动丢弃;磁盘 I/O 具有幂等性(相同数据写入相同位置,且无中间操作)。
  4. 提交信号发出之后,副节点已经知道数据已发送,因此不会重复发送数据。

如何产生一个新的备份

触发条件:当主虚拟机需要重新建立冗余时(如原备份虚拟机失效后),主虚拟机会通知 vSphere 集群服务需要创建新备份。这个新的备份的初始状态是直接复制原主节点产生。接着就像正常的副节点一样,建立日志通道并开始重放模式。

updatedupdated2025-07-302025-07-30