我们实现了一个商业企业级的系统,以提供容错的虚拟机,其基础是通过另一台服务器上的备份虚拟机来复制主虚拟机的执行。我们在VMware vSphere 4.0中设计了一个完整的系统,该系统易于使用,在商品服务器上运行,并且通常使实际应用的性能降低不到10%。此外,在几个实际应用中,保持主虚拟机和副虚拟机同步执行所需的数据带宽低于20 Mbit/s,这使得在更远的距离上实现容错成为可能。一个易于使用的、能在故障后自动恢复冗余的商业系统,除了复制的虚拟机执行外,还需要许多额外的组件。我们已经设计并实现了这些额外的组件,并解决了在支持运行企业应用程序的虚拟机中遇到的许多实际问题。在本文中,我们描述了我们的基本设计,讨论了备选的设计选择和一些实施细节,并提供了微型测试和实际应用的性能结果。
实现容错服务器的一个常见方法是主/备份方法[1],即如果主服务器发生故障,备份服务器总是可以接管的。备份服务器的状态必须始终保持与主服务器几乎相同,这样,当主服务器发生故障时,备份服务器可以立即接管,并且对外部客户隐藏故障,没有数据丢失。复制备份服务器上的状态的一种方法是将主服务器的所有状态的变化,包括CPU、内存和I/O设备,几乎连续地传送到备份服务器上。然而,发送这种状态所需的带宽,特别是内存的变化,可能非常大。
一种不同的复制服务器的方法,可以使用少得多的带宽,有时被称为状态机方法[13]。这个想法是将服务器建模为确定性的状态机,通过从相同的初始状态开始并确保它们以相同的顺序接收相同的输入请求来保持同步。由于大多数服务器或服务有一些操作是不确定的,所以必须使用额外的协调来确保主服务器和备份服务器保持同步。然而,保持主备同步所需的额外信息量远远小于主备中正在变化的状态(主要是内存更新)量。
实现协调以确保物理服务器的确定性执行[14]是困难的,特别是随着处理器频率的提高。相比之下,运行在管理程序之上的虚拟机(VM)是实现状态机方法的一个很好的平台。一个虚拟机可以被认为是一个定义明确的状态机,其操作是被虚拟化的机器(包括其所有设备)的操作。与物理服务器一样,虚拟机有一些非确定性的操作(例如,读取时间时钟或交付中断),因此必须向备份发送额外的信息以确保其保持同步。由于管理程序完全控制了虚拟机的执行,包括所有输入的交付,管理程序能够捕获所有关于主虚拟机上的非确定性操作的必要信息,并在备份虚拟机上正确重放这些操作。
因此,状态机方法可以在商品硬件上为虚拟机实现,不需要对硬件进行修改,可以立即为最新的微处理器实现容错。此外,状态机方法所需的低带宽允许主机和备份有更大的物理分离的可能性。例如,复制的虚拟机可以在分布在园区内的物理机上运行,这比在同一建筑物内运行的虚拟机提供了更多的可靠性。
我们在VMware vSphere 4.0平台上使用主/备份方法实现了容错虚拟机,该平台以一种高效的方式运行完全虚拟化的x86虚拟机。由于VMware vSphere实现了完整的x86虚拟机,我们自动能够为任何x86操作系统和应用程序提供容错。使我们能够记录主程序的执行情况并确保备份的执行情况相同的基础技术被称为确定性重放[15]。VMware vSphere Fault Tolerance(FT)以确定性重放为基础,但加入了必要的额外协议和功能,以构建一个完整的容错系统。除了提供硬件容错外,我们的系统在故障后会自动恢复冗余,在本地集群的任何可用服务器上启动一个新的备份虚拟机。目前,确定性重放和VMware FT的生产版本都只支持单处理器的虚拟机。记录和重放多处理器虚拟机的执行仍在进行中,由于几乎对共享内存的每一次访问都可能是非确定性的操作,因此存在很大的性能问题。
Bressoud和Schneider[3]描述了HP PA-RISC平台的容错虚拟机的原型实现。我们的方法与此类似,但出于性能的考虑,我们做了一些基本的改变,并研究了一些设计方案。此外,我们不得不在系统中设计和实现许多额外的组件,并处理一些实际问题,以建立一个完整的系统,该系统是有效的,可供运行企业应用程序的客户使用。与讨论的其他大多数实用系统类似,我们只试图处理故障停止的故障[12],即在故障的服务器引起外部可见的错误动作之前可以检测到的服务器故障。
本文的其余部分组织如下。首先,我们描述了我们的基本设计,并详细介绍了我们的基本协议,这些协议确保在主虚拟机故障后由备份虚拟机接管时不会丢失数据。然后,我们详细描述了为建立一个强大的、完整的、自动化的系统而必须解决的许多实际问题。我们还描述了在实现容错虚拟机时出现的几种设计选择,并讨论了这些选择中的权衡。接下来,我们给出了我们的实现在一些基准和一些实际企业应用中的性能结果。最后,我们描述了相关工作并得出结论。
图1显示了我们用于容错虚拟机的系统的基本设置。对于我们希望提供容错的特定虚拟机(主虚拟机),我们在不同的物理服务器上运行一个备份虚拟机,该虚拟机与主虚拟机保持同步,执行方式与主虚拟机相同,但有一小段时滞。我们说,这两个虚拟机处于虚拟锁定状态。虚拟机的虚拟磁盘在共享存储上(如光纤通道或iSCSI磁盘阵列),因此主虚拟机和备份虚拟机都可以访问,以便输入和输出。我们将在第4.1节中讨论主用和备用虚拟机拥有独立的非共享虚拟磁盘的设计)。只有主虚拟机在网络上广播它的存在,因此所有的网络输入都会传递给主虚拟机。同样,所有其他输入(如键盘和鼠标)也只到主虚拟机。
主虚拟机收到的所有输入都通过被称为日志通道的网络连接发送到备份虚拟机。对于服务器工作负载,主要的输入流量来自网络和磁盘。如下文第2.1节所述,额外的信息在必要时被传送,以确保备份虚拟机以与主虚拟机相同的方式执行非确定性的操作。其结果是,备份虚拟机的执行方式始终与主虚拟机相同。然而,备份虚拟机的输出被管理程序放弃,所以只有主虚拟机产生的实际输出才会返回给客户。如第2.2节所述,主虚拟机和备份虚拟机遵循特定的协议,包括备份虚拟机的明确确认,以确保在主虚拟机故障时不会丢失数据。
为了检测主用或备用虚拟机是否发生故障,我们的系统采用了相关服务器之间的心跳和对日志通道的流量监测的组合。此外,我们必须确保只有一个主用或备用虚拟机接管执行,即使出现主用和备用服务器相互失去通信的脑裂情况。
在下面的章节中,我们将对几个重要领域提供更多的细节。在第2.1节中,我们给出了一些关于确定性重放技术的细节,该技术确保主用和备用虚拟机通过通过日志通道发送的信息保持同步。在第2.2节中,我们描述了我们的FT协议的一个基本规则,该规则确保在主服务器故障时不会丢失数据。在第2.3节中,我们描述了我们以正确的方式检测和响应故障的方法。
正如我们所提到的,复制服务器(或虚拟机)的执行可以被建模为确定型状态机的复制。如果两个确定性状态机以相同的初始状态启动,并以相同的顺序提供完全相同的输入,那么它们将经历相同的状态序列并产生相同的输出。一个虚拟机有一个广泛的输入集合,包括传入的网络数据包、磁盘读取、以及来自键盘和鼠标的输入。非确定性的事件(如虚拟中断)和非确定性的操作(如读取处理器的时钟周期计数器)也会影响虚拟机的状态。这为复制运行任何操作系统和工作负载的任何虚拟机的执行提出了三个挑战。(1)正确捕捉所有必要的输入和非确定性,以确保备份虚拟机的确定性执行,(2)正确地将输入和非确定性应用于备份虚拟机,以及(3)以不降低性能的方式进行。此外,x86微处理器中的许多复杂操作都有未定义的副作用,因此是非确定性的。捕获这些未定义的副作用并重放它们以产生相同的状态是一个额外的挑战。
VMware确定性重放[15]正是为VMware vSphere平台上的x86虚拟机提供了这种功能。确定性重放记录了虚拟机的输入以及与虚拟机执行相关的所有可能的非确定性,并将其写入日志文件的日志条目流中。以后可以通过从文件中读取日志条目来精确重放虚拟机的执行。对于非确定性操作,记录足够的信息以允许以相同的状态变化和输出来重现该操作。对于非确定性的事件,如定时器或IO完成中断,事件发生的确切指令也被记录下来。在重放过程中,该事件在指令流中的同一位置被传递。VMware确定性重放实现了高效的事件记录和事件交付机制,采用了各种技术,包括使用与AMD[2]和Intel[8]共同开发的硬件性能计数器。
Bressoud和Schneider[3]提到将虚拟机的执行划分为epoch,其中非确定性的事件(如中断)只在epoch结束时交付。epoch的概念似乎被用作一种批处理机制,因为在发生中断的确切指令处单独交付每个中断的成本太高。然而,我们的事件传递机制足够高效,以至于VMware的确定性重放没有必要使用epoch。每个中断在发生时被记录下来,并在重放时在适当的指令处有效地交付。
对于VMware FT,我们使用确定性重放来产生必要的日志条目,以记录主虚拟机的执行情况,但我们没有将日志条目写入磁盘,而是通过日志通道将其发送给备份虚拟机。备份虚拟机实时重放这些条目,因此执行起来与主虚拟机完全一样。然而,我们必须在日志通道上用严格的FT协议来增加日志条目,以确保我们实现容错。我们的基本要求是以下几点。
输出要求:如果备份虚拟机曾经在主虚拟机故障后接管,备份虚拟机将继续执行,其方式与主虚拟机向外部世界发送的所有输出完全一致。
请注意,在故障转移发生后(即备份虚拟机在主虚拟机故障后接管),备份虚拟机开始执行的方式很可能与主虚拟机继续执行的方式完全不同,因为执行过程中发生了许多非确定性事件。然而,只要备份虚拟机满足输出要求,在故障切换到备份虚拟机的过程中就不会丢失外部可见的状态或数据,客户也不会注意到他们的服务中断或不一致。
输出要求可以通过延迟任何外部输出(通常是网络数据包)来确保,直到备份虚拟机收到所有信息,使其至少在输出操作点上重放执行。一个必要条件是,备份虚拟机必须收到输出操作之前产生的所有日志条目。这些日志条目将允许它执行到最后一个日志条目的位置。然而,假设在主服务器执行输出操作后,立即发生了故障。备份虚拟机必须知道,它必须继续重放,直到输出操作的那一刻,并且只在那一刻 "上线"(停止重放并作为主虚拟机接管,如2.3节所述)。如果备份在输出操作前的最后一条日志条目处上线,一些非确定性的事件(例如传递给虚拟机的定时器中断)可能会在它执行输出操作前改变其执行路径。
鉴于上述限制,执行输出要求的最简单方法是在每个输出操作中创建一个特殊的日志条目。然后,输出要求可以通过这个特定的规则来强制执行。
输出规则:主虚拟机不得向外部世界发送输出,直到备份虚拟机收到并确认与产生输出的操作相关的日志条目。
如果备份虚拟机收到了所有的日志条目,包括产生输出的操作的日志条目,那么备份虚拟机将能够准确地重现主虚拟机在该输出点的状态,因此如果主虚拟机死亡,备份将正确地达到与该输出一致的状态。相反,如果备份虚拟机在没有收到所有必要的日志条目的情况下接管,那么它的状态可能会很快发生变化,从而与主虚拟机的输出不一致。输出规则在某些方面类似于[11]中描述的方法,其中 "外部同步 "的IO实际上可以被缓冲,只要它在下一次外部通信之前被实际写入磁盘。
请注意,输出规则并没有说要停止主虚拟机的执行。我们只需要延迟输出的发送,但虚拟机本身可以继续执行。由于操作系统用异步中断做非阻塞的网络和磁盘输出来表示完成,所以虚拟机可以很容易地继续执行,不一定会立即受到输出延迟的影响。相反,以前的工作[3,9]通常表明,在做输出之前,主虚拟机必须完全停止,直到备份虚拟机确认了主虚拟机的所有必要信息。
作为一个例子,我们在图2中展示了一个说明FT协议要求的图表。该图显示了主虚拟机和备份虚拟机上的事件的时间线。从主线到备份线的箭头代表日志条目的传输,从备份线到主线的箭头代表确认。关于异步事件、输入和输出操作的信息必须作为日志条目发送到备份并确认。如图所示,对外部世界的输出被延迟,直到主虚拟机收到备份虚拟机的确认,即它已经收到与输出操作相关的日志条目。鉴于输出规则被遵循,备份虚拟机将能够以与主虚拟机最后一次输出一致的状态接管。
我们不能保证在故障切换的情况下,所有的输出都能准确地产生一次。如果在主服务器打算发送输出时不使用两阶段提交的事务,备份就没有办法确定主服务器是在发送最后一次输出之前还是之后崩溃的。幸运的是,网络基础设施(包括普遍使用的TCP)被设计用来处理丢失的数据包和相同(重复)的数据包。请注意,在主服务器发生故障时,传入主服务器的数据包也可能丢失,因此不会被传递到备份服务器。然而,传入的数据包可能会因为与服务器故障无关的任何原因而丢失,所以网络基础设施、操作系统和应用程序都是为了确保它们能够补偿丢失的数据包而编写的。
如上所述,如果其他虚拟机出现故障,主虚拟机和备份虚拟机必须快速响应。如果备份虚拟机发生故障,主虚拟机将上线--即退出记录模式(因此停止在日志通道上发送条目)并开始正常执行。如果主虚拟机发生故障,备份虚拟机也应同样上线,但这个过程要复杂一些。由于执行的滞后性,备份虚拟机可能会有一些它已经收到并确认的日志条目,但还没有被消耗,因为备份虚拟机还没有到达执行的适当点。备份虚拟机必须继续从日志条目中重放其执行,直到它消耗了最后一个日志条目。在这一点上,备份虚拟机将停止重放模式,并开始作为正常虚拟机执行。实质上,备份虚拟机已经被提升为主虚拟机(现在缺少一个备份虚拟机)。由于它不再是一个备份虚拟机,新的主虚拟机现在将在客户操作系统进行输出操作时向外部世界产生输出。在过渡到正常模式期间,可能需要一些特定设备的操作,以使这种输出正常发生。特别是,为了联网的目的,VMware FT自动在网络上广播新的主虚拟机的MAC地址,这样物理网络交换机就会知道新的主虚拟机位于哪台服务器上。此外,新晋升的主虚拟机可能需要重新发出一些磁盘IO(如第3.4节所述)。
有许多可能的方法来尝试检测主虚拟机和备份虚拟机的故障。VMware FT使用正在运行容错虚拟机的服务器之间的UDP心跳来检测服务器可能已经崩溃的情况。此外,VMware FT监控从主虚拟机发送到备份虚拟机的日志流量以及从备份虚拟机发送到主虚拟机的确认。由于定期的定时器中断,对于一个正常运行的客户操作系统来说,日志流量应该是有规律的,永远不会停止。因此,日志条目或确认信息流的停止可能表明一个虚拟机的故障。如果心跳或日志流量停止的时间超过了特定的超时(在几秒钟的数量级上),就会宣布失败。
然而,任何这样的故障检测方法都很容易受到脑裂问题的影响。如果备份服务器停止接收来自主服务器的心跳,这可能表明主服务器已经失败,或者它可能只是意味着所有的网络连接已经在仍然运行的服务器之间丢失。如果备份虚拟机随后上线,而主虚拟机实际上仍在运行,那么很可能会出现数据损坏和与虚拟机通信的客户端出现问题。因此,我们必须确保在检测到故障时,主虚拟机或备份虚拟机中只有一个会上线。为了避免分脑问题,我们利用了存储虚拟机虚拟磁盘的共享存储。当主用或备用虚拟机想要上线时,它在共享存储上执行一个原子测试和设置操作。如果操作成功,该虚拟机被允许上线。如果操作失败,那么另一个虚拟机肯定已经上线了,所以当前的虚拟机实际上停止了自己("提交自杀")。如果虚拟机在试图进行原子操作时无法访问共享存储,那么它就会等待,直到它能够访问。请注意,如果共享存储因为存储网络的某些故障而无法访问,那么虚拟机很可能无论如何都无法做有用的工作,因为虚拟磁盘驻留在同一个共享存储上。因此,使用共享存储来解决脑裂的情况并没有引入任何额外的不可用性。
该设计的最后一个方面是,一旦发生故障,其中一个虚拟机已经上线,VMware FT就会通过在另一台主机上启动一个新的备份虚拟机来自动恢复冗余性。虽然这个过程在以前的大多数工作中都没有涉及,但它是使容错虚拟机发挥作用的基础,需要精心设计。更多细节见第3.1节。