第2节描述了我们对FT的基本设计和协议。然而,为了创建一个可用的、稳健的和自动的系统,还有许多其他组件必须设计和实施。
必须设计的最大附加组件之一是在与主虚拟机相同的状态下启动备份虚拟机的机制。这一机制也将在故障发生后重新启动备份虚拟机时使用。因此,这个机制必须可以用于处于任意状态(即不是刚启动)的运行中的主虚拟机。此外,我们希望该机制不会严重扰乱主虚拟机的执行,因为这将影响虚拟机的任何当前客户。
对于VMware FT,我们改变了VMware vSphere的现有VMotion功能。VMware VMotion[10]允许将运行中的虚拟机从一台服务器迁移到另一台服务器上,而且干扰最小--虚拟机的暂停时间通常不到一秒钟。我们创建了一种修改过的VMotion形式,在远程服务器上创建一个完全运行的虚拟机副本,但不会破坏本地服务器上的虚拟机。也就是说,我们修改后的FT VMotion将一个虚拟机克隆到远程主机上,而不是迁移它。FT VMotion还设置了一个日志通道,并使源虚拟机作为主设备进入日志模式,而目标虚拟机作为新的备份进入重放模式。与正常的VMotion一样,FT VMotion通常会中断主虚拟机的执行,时间不超过一秒钟。因此,在一个正在运行的虚拟机上启用FT是一个简单的、无干扰的操作。
启动备份虚拟机的另一个方面是选择一台服务器来运行它。容错虚拟机在可访问共享存储的服务器集群中运行,因此所有虚拟机通常可以在集群的任何服务器上运行。这种灵活性使VMware vSphere即使在一台或多台服务器发生故障时也能恢复FT冗余。VMware vSphere实现了一个维护管理和资源信息的集群服务。当故障发生时,主虚拟机现在需要一个新的备份虚拟机来重新建立冗余,主虚拟机会通知集群服务它需要一个新的备份。集群服务根据资源使用情况和其他约束条件确定运行备份虚拟机的最佳服务器,并调用FT VMotion来创建新的备份虚拟机。其结果是,VMware FT通常可以在服务器发生故障的几分钟内重新建立虚拟机冗余,而容错虚拟机的执行不会出现任何明显的中断。
在管理日志通道的流量方面,有许多有趣的实现细节。在我们的实现中,管理程序为主用和备用虚拟机的日志条目维护一个大的缓冲区。当主虚拟机执行时,它产生日志条目到日志缓冲区,同样,备份虚拟机从其日志缓冲区消耗日志条目。主虚拟机的日志缓冲区的内容会尽快刷新到日志通道,而日志条目一旦到达,就会从日志通道读入备份虚拟机的日志缓冲区。每次备份从网络上读取一些日志条目到其日志缓冲区时,都会向主服务器发送确认信息。这些确认允许VMware FT确定何时可以发送被输出规则延迟的输出。图3说明了这个过程。
如果备份虚拟机在需要读取下一个日志条目时遇到一个空的日志缓冲区,它将停止执行,直到有一个新的日志条目可用。
由于备份虚拟机没有进行外部通信,这种暂停不会影响虚拟机的任何客户端。
同样地,如果主虚拟机在需要写一个日志条目时遇到一个满的日志缓冲区,它必须停止执行,直到日志条目可以被刷新出来。
这种停止执行是一种自然的流量控制机制,当主虚拟机以过快的速度产生日志条目时,它会减慢速度。
然而,这种暂停会影响到虚拟机的客户,因为主虚拟机将完全停止,没有反应,直到它可以记录其条目并继续执行。
因此,我们的实现必须被设计为尽量减少主日志缓冲区填满的可能性。
主日志缓冲区可能被填满的一个原因是,备份虚拟机的执行速度太慢,因此消耗日志条目的速度太慢。一般来说,备份虚拟机必须能够以与主虚拟机记录执行的速度大致相同的速度回放执行。幸运的是,在VMware的确定性重放中,记录和重放的开销大致是一样的。然而,如果托管备份虚拟机的服务器被其他虚拟机严重加载(因此资源过度承诺),备份虚拟机可能无法获得足够的CPU和内存资源,无法像主虚拟机那样快速执行,尽管备份管理程序的调度器做出了最大努力。
除了避免在日志缓冲区填满的情况下出现意外停顿外,还有一个原因是我们不希望执行滞后变得太大。如果主虚拟机发生故障,备份虚拟机必须 "catch up 迎头赶上",重新播放它在上线并开始与外部世界通信之前已经确认的所有日志条目。完成重放的时间基本上是故障点的执行滞后时间,所以备份上线的时间大致等于故障检测时间加上当前执行滞后时间。因此,我们不希望执行滞后时间很大(超过一秒),因为这将给故障转移时间增加大量时间。
因此,我们有一个额外的机制减缓主虚拟机的速度,以防止备份虚拟机落后太多。在我们发送和确认日志条目的协议中,我们发送额外的信息,以确定主用和备用虚拟机之间的实时执行滞后。通常情况下,执行滞后小于100毫秒。如果备份虚拟机开始有明显的执行滞后(例如,超过1秒),VMware FT开始放慢主虚拟机的速度,通知调度器给它稍小的CPU数量(最初只有百分之几)。我们使用一个缓慢的反馈回路,它将尝试逐步确定主虚拟机的适当CPU限制,使备份虚拟机能够匹配其执行。如果备份虚拟机继续落后,我们就继续逐步减少主虚拟机的CPU限制。反之,如果备份虚拟机赶上了,我们就逐渐增加主虚拟机的CPU限制,直到备份虚拟机恢复到有一点滞后。
请注意,主虚拟机的这种减速是非常罕见的,通常只有在系统处于极端压力下才会发生。第5节的所有性能数字都包括任何此类减速的成本。
另一个实际问题是处理可能适用于主虚拟机的各种控制操作。例如,如果主虚拟机明确关闭了电源,那么备份虚拟机也应该停止,并且不尝试上线。再比如,主虚拟机上的任何资源管理变化(如增加CPU份额)也应该应用到备份上。对于这类操作,特殊的控制条目会通过日志通道从主服务器发送到备份服务器,以便对备份服务器进行适当的操作。
一般来说,对虚拟机的大多数操作应该只在主虚拟机上启动。然后,VMware FT发送任何必要的控制条目,以在备份虚拟机上引起适当的变化。唯一可以在主虚拟机和备份虚拟机上独立完成的操作是VMotion。也就是说,主虚拟机和备份虚拟机可以独立地被VMotion到其他主机上。请注意,VMware FT确保两个虚拟机都不会被移动到其他虚拟机所在的服务器上,因为这种情况将不再提供容错。
主虚拟机的VMotion比普通的VMotion增加了一些复杂性,因为备份虚拟机必须从源主虚拟机断开连接,并在适当的时间重新连接到目标主虚拟机。备份虚拟机的VMotion有一个类似的问题,但增加了额外的复杂性。对于正常的VMotion,我们要求在VMotion的最终切换发生时,所有未完成的磁盘IO都被静态化(即完成)。对于主虚拟机来说,通过等待物理IO完成并将这些完成交付给虚拟机,这种静止是很容易处理的。但是,对于备份虚拟机来说,没有简单的方法可以使所有IO在任何需要的时间点上完成,因为备份虚拟机必须重放主虚拟机的执行,并在同一执行点上完成IO。主虚拟机可能正在运行一个工作负载,其中在正常执行期间总是有磁盘IO在飞行。VMware FT有一个独特的方法来解决这个问题。当一个备份虚拟机处于VMotion的最终切换点时,它通过日志通道请求主虚拟机暂时停止其所有IO的运行。然后,备份虚拟机的IO在复制主虚拟机执行静止操作时,自然也会在一个执行点被静止。
有一些与磁盘IO相关的微妙的实现问题。首先,鉴于磁盘操作是无阻塞的,因此可以并行执行,同时访问同一磁盘位置的磁盘操作会导致非确定性。另外,我们对磁盘IO的实现使用DMA直接进入/离开虚拟机的内存,所以同时进行的磁盘操作访问相同的内存页也会导致非决定性。我们的解决方案通常是检测任何这样的IO竞争(这是很罕见的),并强制这种竞争性的磁盘操作以相同的方式在主机和备份上顺序执行。
其次,磁盘操作也可能与虚拟机中的应用程序(或操作系统)的内存访问发生竞争,因为磁盘操作通过DMA直接访问虚拟机的内存。例如,如果虚拟机中的应用程序/操作系统在读取一个内存块的同时对该块进行磁盘读取,可能会出现非决定性的结果。这种情况也不太可能,但我们必须检测到它,并在它发生时加以处理。一个解决方案是在作为磁盘操作目标的页面上临时设置页面保护。如果虚拟机碰巧访问了一个也是未完成磁盘操作的目标的页面,那么页面保护就会导致一个trap陷阱,虚拟机可以暂停,直到磁盘操作完成。由于改变MMU对页面的保护是一个昂贵的操作,我们选择使用回弹缓冲区。回弹缓冲区是一个临时缓冲区,其大小与磁盘操作所访问的内存相同。一个磁盘读取操作被修改为读取指定的数据到缓冲区,而数据只在IO完成时被复制到客户内存。同样,对于磁盘写操作,要发送的数据首先被复制到缓冲区,磁盘写被修改为从缓冲区写入数据。使用回弹缓冲区会减慢磁盘操作,但我们没有看到它造成任何明显的性能损失。
第三,有一些与磁盘IO有关的显著问题(如未完成),当故障发生时,主磁盘上的IO未完成,而备份机开始接管。新晋级的主虚拟机没有办法确定磁盘IO是否已经发出或成功完成。此外,因为磁盘IO没有在备份虚拟机上从外部发出,所以在新晋升的主虚拟机继续运行时,不会有明确的IO完成,这最终会导致虚拟机中的客户操作系统启动中止或重置程序。我们可以发送一个错误完成,表明每个IO都失败了,因为即使IO成功完成,返回一个错误也是可以接受的。然而,客户操作系统可能对来自其本地磁盘的错误反应不大。相反,我们在备份虚拟机的上线过程中重新发出待定IO。因为我们已经消除了所有的竞争,而且所有的IO都直接指定访问哪些内存和磁盘块,所以这些磁盘操作即使已经成功完成,也可以重新发布(即它们是等效的)。
VMware vSphere 为虚拟机网络提供了许多性能优化。其中一些优化是基于管理程序异步更新虚拟机的网络设备的状态。例如,接收缓冲区可以在虚拟机执行时由管理程序直接更新。不幸的是,这些对虚拟机状态的异步更新增加了非确定性。除非我们能保证所有的更新都发生在主程序和备份程序的指令流中的同一个点上,否则备份程序的执行可能会与主程序的执行出现偏差。
对FT的网络仿真代码最大的改变是禁用异步网络优化。用传入的数据包异步更新虚拟机环形缓冲区的代码已被修改,以迫使客户向管理程序进行捕获,在那里它可以记录更新,然后将其应用于虚拟机。同样,通常从传输队列中异步拉出数据包的代码在FT中被禁用,而是通过向管理程序发出陷阱来完成传输(下文指出的情况除外)。
消除网络设备的异步更新与第2.2节中描述的发送数据包的延迟相结合,为网络提供了一些性能挑战。我们已经采取了两种方法来改善运行FT时的虚拟机网络性能。首先,我们实施了集群优化,以减少虚拟机的陷阱和中断。当虚拟机以足够的比特率进行数据流时,管理程序可以对每组数据包进行一次传输陷阱,在最好的情况下,陷阱为零,因为它可以将数据包作为接收新数据包的一部分进行传输。同样,管理程序可以通过只发布一组数据包的中断来减少对虚拟机传入数据包的中断次数。
我们对网络的第二个性能优化涉及减少传输数据包的延迟。如前所述,管理程序必须延迟所有传输的数据包,直到它从备份中得到适当的日志条目的确认。减少传输延迟的关键是减少向备份发送日志信息并获得确认的时间。我们在这方面的主要优化涉及确保发送和接收日志条目和确认都可以在没有任何线程上下文切换的情况下完成。VMware vSphere管理程序允许在TCP堆栈中注册一些函数,每当收到TCP数据时,这些函数就会从一个延迟执行的上下文(类似于Linux中的tasklet)中被调用。这使我们能够快速处理备份上的任何传入的日志消息和主虚拟机收到的任何确认,而不需要任何线程上下文切换。此外,当主虚拟机排队等待传输数据包时,我们通过调度延迟执行上下文来强制立即刷新相关的输出日志条目(如第2.2节所述),以进行刷新。
在我们实现VMware FT的过程中,我们已经探索了许多有趣的设计方案。在本节中,我们将探讨其中的一些替代方案。
在我们的默认设计中,主用和备用虚拟机共享相同的虚拟磁盘。因此,共享磁盘的内容自然是正确的,而且在发生故障切换时也是可用的。从本质上讲,共享磁盘被认为是主用和备用虚拟机的外部,所以任何对共享磁盘的写入都被认为是对外部世界的通信。因此,只有主虚拟机对磁盘进行实际写入,而对共享磁盘的写入必须根据输出规则进行延迟。
另一种设计是主虚拟机和备份虚拟机拥有独立的(非共享的)虚拟磁盘。
在这种设计中,备份虚拟机确实对其虚拟磁盘进行了所有的磁盘写入,在这样做的过程中,它自然会使其虚拟磁盘的内容与主虚拟机的虚拟磁盘的内容保持同步。
图4说明了这种配置。
在非共享磁盘的情况下,虚拟磁盘基本上被认为是每个虚拟机的内部状态的一部分。
因此,根据输出规则,主磁盘的写入不需要延迟。
非共享设计在主虚拟机和备份虚拟机无法访问共享存储的情况下相当有用。
这种情况可能是因为共享存储不可用或太贵,或者因为运行主用和备用虚拟机的服务器相距甚远("远距离FT")。
非共享设计的一个缺点是,在首次启用容错时,必须以某种方式明确同步虚拟磁盘的两个副本。
此外,磁盘在故障后可能会失去同步,因此在故障后重新启动备份虚拟机时,必须明确地重新同步它们。
也就是说,FT VMotion不仅要同步主用和备份虚拟机的运行状态,而且还要同步它们的磁盘状态。
在非共享磁盘配置中,可能没有共享存储可用于处理大脑分裂的情况。在这种情况下,系统可以使用一些其他的外部分割器,例如两个服务器都可以对话的第三方服务器。如果服务器是一个有两个以上节点的集群的一部分,系统可以替代性地使用基于集群成员的多数算法。在这种情况下,只有当一个虚拟机运行在一个包含大多数原始节点的通信子集群的服务器上时,它才会被允许上线。
在我们的默认设计中,备份虚拟机从不从其虚拟磁盘(无论是共享还是非共享的)中读取数据。由于磁盘读取被认为是一种输入,所以自然会通过日志通道将磁盘读取的结果发送给备份虚拟机。
另一种设计是让备份虚拟机执行磁盘读取,从而消除磁盘读取数据的日志记录。对于进行大量磁盘读取的工作负载,这种方法可以大大减少日志通道上的流量。然而,这种方法有许多微妙之处。它可能会减慢备份虚拟机的执行速度,因为备份虚拟机必须执行所有的磁盘读取,如果它们在到达虚拟机执行过程中在主服务器上完成的时候还没有物理上的完成,则必须等待。
另外,必须做一些额外的工作来处理失败的磁盘读取操作。如果主磁盘读取成功,但备份的相应磁盘读取失败,那么备份的磁盘读取必须重试,直到它成功,因为备份必须在内存中获得与主磁盘相同的数据。相反,如果主磁盘读取失败,那么目标内存的内容必须通过日志通道发送给备份,因为内存的内容将是不确定的,不一定会被备份虚拟机的成功磁盘读取复制。
最后,如果这种磁盘读取方式与共享磁盘配置一起使用,还有一个微妙的问题。如果主虚拟机对某一特定磁盘位置进行了读取,随后相当快地对同一磁盘位置进行了写入,那么磁盘写入必须延迟到备份虚拟机执行了第一次磁盘读取之后。这种依赖性可以被正确检测和处理,但会给实现增加额外的复杂性。
在第5.1节中,我们给出了一些性能结果,表明在备份上执行磁盘读取会导致实际应用的吞吐量略有下降(1-4%),但也会明显地减少日志带宽。因此,在备份虚拟机上执行磁盘读取,在日志通道的带宽相当有限的情况下,可能是有用的。