容错虚拟机
约 3533 字大约 12 分钟
2025-12-08
介绍
最常见的实现容错服务器的策略,就是主从备份的方法。当主服务器崩溃的时候,备份服务器可以接替主服务器的工作。但是备份服务器要和主服务器保持一样的状态,需要同步CPU,内存,磁盘,I/O等设备的数据,需要消耗大量的带宽。
另一种不同的想法实现上面的策略,使用较少的带宽,让备份与主服务器保持同步。这种想法的模型可以看做一个确定的状态机,两台机器从相同的初始化状态开始,按顺序接收并执行相同的操作,就可以实现同步了。 尽管有效操作是不确定的,这需要其他额外的同步机制。
想要在实际的物理机器上实现上面的确定性状态机,是十分困难的,因此只能在Hypervisor上运行虚拟机(virtual machine)来实现。这里可以将虚拟机视为一个定义明确的状态机。
记录主虚拟机的执行并确保备虚拟机完全相同地执行的基础技术被称为确定性重放(deterministic replay)。
目前只能在单处理器的虚拟机,因为多处理器中访问共享内存是不确定性的操作。
我们仅尝试处理“故障-停止”(fail-stop)类型的故障,这类服务器故障能够在故障服务器导致错误的外部可见行为之前被检测到。
论文剩下的部分将按下面的顺序来介绍,首先,描述最重要的协议的设计与细节,该协议保证了如果副节点接管发生故障的主服务器,没有数据丢失。然后我们讨论一些必须处理的实际问题的细节。接着我们会描述实现容错虚拟机中出现的若干设计问题。
基础设计

图片中展示了设计的基本架构,主虚拟机和备虚拟机分别运行在两个不同的物理机器上。两台虚拟机保持同步并执行相同的操作,尽管存在微小的时间延迟,我们称为虚拟锁步状态。 虚拟磁盘是主虚拟机和备虚拟机共享的,主机和备份机机都可以访问这些磁盘进行输入输出。
所有主机的操作都会通过日志通道(LogChannel)进行同步,发送到备份机上。同时还会有传输其他额外的信息来保证同步。服务器负载,主要的输入流量来自网络和硬盘, 备份机的输出会被Hypervisor丢弃,因此只有主机输入数据才会发送给客户端。
使用心跳机制来检测主机和备用机是否存活或者任务失败。
确定性重放的设计
确定性的事件有一系列的输入,即将到来的网络包,磁盘的读写,键盘鼠标的输入。
不确定性的事件有虚拟机的中断,和处理器的时钟计数。
三个挑战
- 正确捕获所有确定性和不确定性的事件
- 在备用虚拟机上正确应用输入和不确定性的事件
- 不影响性能的情况下完成
将虚拟机切分成不同的epoch,非确定性事件,仅在一个epoch结束时传递。确定性重放,则不需要划分epoch。
FT 协议
主机和备用机,通过日志通道来同步。
输出要求:如果备用机在主机发生故障后接管,备用机将继续用一种与主机对外输出完全一致的方式来执行。
输出规则:主机可能不发送输出到外部,知道备用机收到并确认主机产生输出操作相关的日志。
尽管有些不确定性的事件,只要备份满足输出要求,在故障转移期间没有外部可见状态或数据的丢失,将认为服务没有中断或者一致。
如果备份机已经收到所有的日志,能准确重现主机的状态,主要没有收到完整的日志,就接管了,状态可能会产生分歧,导致与主服务器输出不一致。

由于未使用两阶段提交等强一致性机制,无法严格保证每个输出“恰好产生一次”——当主机崩溃时,备份无法判断该输出是否已被发出。所幸的是,现有网络基础设施(如TCP)本身具备处理丢包和重复包的能力;同时,传入主VM的数据包也可能因故障丢失,但这类丢包在网络、操作系统和应用层面本就需被容忍和恢复,因此系统整体仍能保持鲁棒性。
检测与故障响应
主机和备用机之前使用生产者消费者的模型,主机向日志通道写入日志,备用机从通道中取出日志进行重放,直到消耗完了。
出于联网方面的考虑,FT会通过新的虚拟机的mac地址。
使用udp心跳来检测故障。
为了防止出现脑裂的情况,使用共享存储作为协调机制,需在共享存储上执行一个原子性的检测并设置(test-and-set)操作。 成功则允许,失败则说明有另一虚拟机在运行,当前虚拟机会自杀终止。
一旦主机发生故障有备用机接管,系统会自动在另一台主机上启动新的备份虚拟机。
实际执行
启动与重启
主要的机制就是启动一个和主机状态一样的备份机。当故障发生是,这个机制也变得很有用。
VMware FT 改造了 vSphere 平台已有的 VMotion 功能:不同于传统 VMotion 的“迁移”,FT VMotion 会将主 VM 克隆到远程主机上,同时保留原 VM,并自动建立日志通道——源 VM 进入日志记录模式(作为主),目标 VM 进入重放模式(作为备份)。整个过程通常仅造成不到一秒的短暂暂停,几乎无感。
此外,当故障发生、需重建冗余时,系统通过 vSphere 集群服务 自动选择最优服务器来部署新备份 VM。该服务基于资源使用情况和约束条件做出调度决策,并触发 FT VMotion 完成克隆。得益于共享存储和集群管理,VMware FT 能在几分钟内自动恢复冗余,全程对客户端透明、无明显中断。
管理日志通道

管理程序会维持一个大的缓冲区。主机生成日志到缓冲区,备份从缓冲区中消耗日志。
主日志缓冲区可能填满的原因之一是备份机执行速度太慢,因此消耗日志条目太慢。一般来说,备份机必须能够以与正在记录执行的主VM大致相同的速度重放执行。
如果备份 VM 有一个显著的执行滞后(例如,超过 1 秒),VMware FT 通过通知调度程序给它稍微少一点的CPU(最初只是百分之几)来减慢主 VM。
FTVM上的操作
在 VMware FT 中,主 VM 上的各类控制操作(如关机、调整 CPU 资源等)必须同步到备份 VM,以保持一致性。为此,系统通过日志通道发送特殊的控制条目,确保备份 VM 执行相应的操作(例如一同关闭,而非尝试接管)。原则上,所有操作都应在主 VM 上发起,由 FT 自动同步至备份 VM。
唯一例外是 VMotion:主 VM 和备份 VM 可各自独立迁移到不同主机,但 VMware FT 会确保两者不会被迁移到同一台物理服务器,以维持容错能力。
然而,VMotion 在 FT 环境下更复杂:
- 主 VM 迁移时,备份 VM 会短暂断开并重新连接日志通道,相对容易处理。
- 备份 VM 迁移则面临特殊挑战:由于备份 VM 必须严格重放主 VM 的执行轨迹(包括 I/O 操作),无法像普通 VM 那样简单等待本地 I/O 完成。
- 为解决此问题,VMware FT 采用独特机制:在备份 VM 执行 VMotion 最终切换前,通过日志通道通知主 VM 暂停所有磁盘 I/O;主 VM 停止后,其状态(包括 I/O 停止点)被记录并发送,备份 VM 在重放该状态时自然同步停止 I/O,从而保证一致性。
磁盘IO的实现问题
为确保主备 VM 在磁盘 I/O 上的执行确定性与状态一致,VMware FT 采用三项关键机制:
- 串行化竞争 I/O:检测并强制并发访问相同磁盘位置或内存页的非阻塞磁盘操作在主备 VM 上按相同顺序执行,消除非确定性。
- 使用弹跳缓冲区(Bounce Buffer):避免 DMA 直接读写 VM 内存导致的 CPU 与 I/O 竞争。所有磁盘 I/O 先作用于临时 bounce buffer,再同步拷贝到/从 VM 内存,使 I/O 可被日志记录并在备份端精确重放。
- 幂等重试悬挂 I/O:故障切换时,对主 VM 未完成的 I/O 不返回错误,而是在新主 VM 上安全重发。因前述机制已确保 I/O 操作幂等,重复执行不会破坏一致性,从而避免操作系统异常。
网络IO的实现问题
为保证主备VM执行一致,VMware FT禁用网络异步优化,改用同步陷阱记录所有网络操作。为弥补由此带来的性能损失,FT采用两项核心优化:
集群化陷阱与中断合并: 当 VM 以较高吞吐率收发数据时,管理程序将多个数据包批量处理——对一组包仅触发一次传输 trap 或接收中断,显著减少上下文切换和中断开销,在理想情况下甚至可实现“零 trap”。
低延迟日志确认路径: 为加速主备之间的日志确认、从而减少发送延迟,FT 利用 vSphere 管理程序的延迟执行上下文(类似 Linux tasklet):
- 在 TCP 栈中注册回调函数,一旦收到备份 VM 的日志确认或日志消息,立即在软中断上下文中处理,避免线程上下文切换;
- 当主 VM 需要发送数据包时,主动调度一个延迟执行上下文来强制刷出对应的输出日志条目,确保日志尽快送达备份端。
整个“日志发送 → 备份确认 → 主 VM 发包”的闭环,都在内核软中断层面完成,不涉及用户态或线程调度,因此延迟极低。
一些替代设计
共享和非共享磁盘

VMware FT 支持两种磁盘架构以适应不同部署需求:
- 共享磁盘模式(默认): 主备 VM 共用同一虚拟磁盘,故障切换后数据天然一致。 磁盘写入被视为“对外输出”,仅由主 VM 执行且必须延迟,直到备份 VM 确认日志条目(遵循输出规则)。
- 非共享磁盘模式(用于无共享存储或长距离容错): 主备各自拥有独立磁盘副本,备份 VM 重放所有磁盘写入以保持同步。 磁盘被视为内部状态,因此主 VM 的写入无需延迟,提升性能。 缺点:初始需显式同步双份磁盘;故障后重启时也需重新同步磁盘状态;FT VMotion 必须同时同步内存状态和磁盘内容。
- 脑裂防护机制(非共享场景下): 依赖外部仲裁(如第三方服务)或集群多数派(majority)算法决定哪个 VM 可上线,确保只有一个活跃实例,避免数据分裂。
在备份VM上执行磁盘读
VMware FT 默认不在备份 VM 上执行磁盘读取,而是将主 VM 的读取结果作为“输入”通过日志通道发送给备份 VM,确保确定性重放。
替代方案:允许备份 VM 直接执行磁盘读取,可显著减少日志流量(尤其对读密集型负载),但带来三方面挑战:
- 性能同步问题:备份 VM 可能因 I/O 延迟而落后于主 VM,需等待读取完成才能继续执行,轻微降低吞吐(实测下降 1–4%)。
- 错误处理复杂性:
- 若主成功而备失败,必须重试备份读取直至成功;
- 若主失败,其内存中的“损坏数据”必须通过日志显式传给备份,否则备份的本地读取会得到错误结果。
- 共享磁盘下的依赖冲突:若主 VM 先读再写同一位置,写操作必须延迟至备份完成对应读取,需额外机制检测和协调此类依赖。
注释
VM FT 是如何处理网络分区的?也就是说,如果主 VM 和备份 VM 被分隔到不同的网络分区中,是否可能出现备份 VM 也变成主 VM,导致系统同时运行两个主 VM 的情况?
不会。VM FT 通过共享存储上的互斥锁机制防止网络分区导致的脑裂问题。只有能成功获取锁的 VM 才能作为主 VM 运行,从而保证系统中始终至多一个主 VM。即使在网络分区的情况下,也不可能同时出现两个主 VM(primary)。