分布式之VMwareFT
0. 前言
VMware公司在2010年发表了一篇有关虚拟机容错的论文:《The Design of a Practical System for Fault-Tolerant Virtual Machines》。同时,它也是MIT6.824 LEC4的Preparation Read。本文将结合课程简单介绍论文内容。
1. 主/备份
实现服务器容错的一种通用方法是主/备份(primary/backup) 方法,当主服务器失效,备份服务器可以接管主服务器的工作。
为了保持主备状态同步,有2种方法:
**状态传递(State transfer)**。将主服务器上的所有状态(包括CPU、内存等等)都发送给备份服务器。这种方法能保证主备状态一致,但每次需同步大量数据,会占用大量宽带。
**复制状态机(Replicated state machine)**。主备服务器一开始处于相同状态,对于每一条发送给主服务器的输入请求(比如将一个数加1),主服务器会发送给备份服务器,它们以相同顺序执行输入请求,从而保持同步。这种方法同步的信息较少,效率更高。但是,对于一些不确定请求,比如获取当前时间、获取随机数、多核计算,主备服务器执行结果可能并不相同,还需要额外的操作来保持同步。
VMwareFT论文种使用了复制状态机来实现容错。
2. 基本设计
主备虚拟机运行在不同的机器上,它们共享一个磁盘(Shared Disk),主VM接收到的输入都通过日志通道(Logging channel)发送给备份VM。主备VM都执行相同操作,但只有主VM的输出才返回给客户端。
系统通过相关服务器之间的心跳和日志通道上的流量监控,来检测主备VM是否失效。
确定性重放
对于不确定事件或操作,比如中断、获取时间等,可能会导致主备VM状态不一致。
VMware确定性重放(deterministic replay)机制,能够捕获所有输入和所有可能不确定输入,并记录到日志文件,通过读取日志,备份VM可以准确地重放执行。
对于不确定输入,必须记录足够的信息来保证重放。当然论文中并未介绍是哪些信息,一种可能的方式是直接记录执行结果,备份VM只需复制结果即可,而不需要再次执行。而对于多核计算,暂时没有有效机制来保证执行结果一致,因此论文中的虚拟机VM都规定是单核的。
(注:这一节为什么讲了跟没讲一样)
FT 协议
考虑一种情况:VM上有一个数A值为10,客户端请求将A+=1并返回结果,主VM接收请求后执行+1并返回11,但它正要将该请求告诉给备份VM时宕机了。备份VM接替了主VM,但由于备份VM并未收到请求,A的值依旧是10。此时,客户端再请求将A+=1并返回,它再次得到11,显然这是不正确的。
为了防止这种情况的发生,论文设计了一条输出规则:对于需要输出的请求,主VM会等备份VM确认收到请求后(备份VM会返回ACK),再将输出返回。同时,主VM只是延迟输出,并不会阻塞后面的任务(异步)。
但是,规则并不能保证结果只输出一次。比如:当主VM收到备份VM的ACK后,正要返回输出,但突然宕机了。备份VM接替之后,并不知道主VM是否已经返回输出,所以它会将最后一次输出再返回一次(其实之前的输出备份VM都不知道是否返回吧?)。当重复输出并无大碍,比如:对磁盘同一个位置重复写,一个数据包发两次(TCP能够检测重复的数据包)。
检测和故障响应
主VM和备VM之间会通过UDP心跳和监控日志通道流量来检测对方服务是否正常,若停止的时间超过阈值,就说明出现故障。
不过这种检测方法并不能避免脑裂问题(split-brain) 。当备份VM长时间没有收到主VM的心跳和日志消息,可能是主VM宕机,也可能是网络问题。如果是因为暂时的网络问题,此时备份VM若顶替主VM,那网络上将出现2个主VM。
为了解决这个问题,VMware在共享磁盘上设置了test-and-set的原子操作 ,类似于一个锁,只能被一个VM获取,而只有拿到这个锁才能对共享磁盘进行操作。
3. FT实际应用
创建备份VM
VMware使用VMware vSphere现有的VMotion功能:将一个运行中的VM克隆到新机器上,而VM只需暂停不到1秒的时间。
同时,VMware vSphere实现了一个集群服务,它会根据主VM的备份请求选择合适的机器,然后通过VMotion进行克隆,这过程只需几分钟。
(细节啥也没说)
管理日志通道
为了管理监控日志通道,管理程序为日志通道维持了一个日志缓冲区,主VM向缓冲区写,备份VM从缓冲区读。
当缓冲区被填满(主VM写太快,备份VM读太慢)时,主VM将会停止执行,等待缓冲区有空闲。显然,这会影响客户端体验。为此,当缓冲区日志太多时,管理程序会降低主VM的执行速度(比如分配更少的CPU资源),等待备份VM消化缓存,更上日志进度。
当然,这种主VM减速很罕见。
4. 个人总结
不同于数据库备份,虚拟机这种整个操作系统的备份确实困难且复杂。因为它存在许多不确定操作,比如中断、多核并行等,复现难度大。
个人认为, 对于操作繁多且复杂的场景,应该避免做操作的同步,而应该做数据状态的同步 。比如,客户端请求对A进行+1操作,那么不应该让备份机器同步执行这个+1操作,而应该将A进行+1的结果同步给备份机器。当然,这其实就是在状态转移和复制状态机两种方式间做抉择。
这篇论文虽然并不详细,也没有带来多大的影响,但也有值得借鉴的点:
主VM需要确定备份VM获取到请求后,再返回给客户端。不过要是备份VM执行失败了怎么办?
通过外部权威来解决脑裂问题。感觉不如超半数投票?