分布式系统 - 基于虚拟机的主从复制
本文主要参考论文《The Design of a Practical System for Fault-Tolerant Virtual Machines》和MIT6.824 lecture4而写。本文将这篇论文简写为VM-FT。
1 VM-FT原理
1. 为什么要主从复制?
当一台服务器挂掉后,应该怎么办呢?其中一个解决方案是,提前复制这台服务器。本文将原始服务器称为主机,将复制的服务器称为从机。
1.2 复制方式
复制一台服务器的方式有两种:
- 状态转移:主机将自己所有的状态(包括CPU、内存、I\O设备等)全部发给从机。
- 复制状态机:两台服务器以同样的状态启动。当客户端向主机发送请求时,主机将该请求原封不动地发送给从机。这样,主机和从机以同样的状态启动,且以同样的顺序执行同样的命令,那么它们的结果自然也就一样了。
显然,状态转移这种方式传输的数据量比复制状态机这种方式要大得多。因此,VM-FT选择使用了复制状态机方式进行复制。
1.3 VM-FT结构
VM-FT是基于虚拟机实现的主从复制,其结构如下图所示。其中,主机和从机处在同一个网络中,且共用一个磁盘。主机接收到的命令,将通过Logging channel传给从机。随后,主机和从机都会执行这些命令以保持一致。
1.4 确定性重放
我们现在已经知道了主机复制的大概原理,但是要真正实现完全的主从复制还有三大难点:
- 主机正确获取所有的输入和非确定性输入
- 从机执行主机传来的所有输入和非确定输入,并获得和主机一致的结果
- 在实现上述两个目标的同时,还不能降低系统的性能
VM-FT中提到VMware deterministic replay技术解决了这些难点,具体可见论文《ReTrace: Collecting Execution Trace with Virtual Machine Deterministic Replay》。
1.5 FT协议
现在,我们思考一种情况。主机中有一个数据为10。这时,客户端发送了一个请求,想要将这个数据加一。主机接收了这个请求,将这个数据+1得到了11,并将这个11返回给了客户端。突然,主机崩溃了,且还没有给从机发送客户端的这个请求。由于主机崩溃了,从机接管了主机,但是从机还未收到主机的+1指令,因此从机的这个数据仍然是10。这是,客户端又发送了一个加1的请求,但接管的从机中存的数据仍然是10,那么返回给客户端的数据仍然是11。客户端此时就觉得很奇怪了,明明请求了两次加1,为什么得到的结果只加了1次呢?
为了避免上述的问题,VM-FT提出了FT协议。该协议制定了输出规则,规则如下:
- 主机在将得到的输出发送给客户端之前,需要收到从机传来的确认请求(确保从机收到了主机传去的同步消息)。
- 主机在确定要将输出发送给客户端之前,不会停滞,而是会执行后面的任务。(这样就可以将等待的时间利用起来了)
FT协议的时间序列图如下图所示:
现在,我们再思考一种情况。当主机收到了从机的确认请求,且将输出发给了客户端后,主机挂掉了。此时,从机开始接管,但从机不知道主机是否发送了输出给客户端,那该怎么办呢?VM-FT提到,既然输出是通过网络发送的,那TCP之类的网络协议是可以检测出重复的数据包的。
1.6 监测并处理故障
监测
通过UDP心跳包和Logging channel,主机和从机可以快速知道另一方是否出现故障。UDP心跳包是定时发送的一个自定义的结构体,用来让对方知道自己还在运行。Logging channel中,有主机向从机发送的日志记录,也有从机向主机发送的确认信息。如果UDP心跳包和Logging channel的信息超过一段时间没有被检测到,就认为有故障发生了。
处理故障
当我们检测到故障后,从机是不是会想接管主机来运行呢?但如果这样的故障是因为网络的问题引起的,而不是主机出现问题呢?此时,主机和从机都在运行了。那与这台主机交互的客户端就会收到多个回复,这样就不对了,这个问题就是split-brain问题。为了避免split-brain问题,VM-FT在共享存储上(主机和从机共享一个磁盘,见VM-FT结构图)执行一个原子指令test-and-set。该指令伪代码如下:
test-and-set() {
acquire_lock()
if flag == true:
release_lock()
return false
else:
flag = true
release_lock()
return true
}
当主机还在运行时,flag已经被设置为true了。此时,从机想去接管的话,需要去查询这个flag变量,发现flag已经为true了,从机就不会接管了。这样,也就避免了split-brain的问题。
2 VM-FT的具体实现细节
2.1 创建从机
前面我们说到从机需要和主机以同样的状态启动,那么怎么样让从机和主机以同样的状态启动呢?VMware公司提供了一个名为VMware VMotion的工具,该工具允许以最小化中断的代价,将正在运行的虚拟机从一台服务器迁移到另一台服务器上。该操作只需要不到1秒的时间就能完成。为了实现备份的目的,VM-FT对VMotion工具做了略微的修改。VM-FT让VMotion在另一台服务器上创建一个虚拟机备份后,并不销毁当前服务器的虚拟机。
2.2 管理Logging Channel
主机和从机各自有一个Log buffer,主机将产生的日志写入主机的Log buffer,然后通过Logging channel传给从机的Log buffer,从机再从自己的Log buffer中读取日志。
当从机的Log buffer空了的时候,从机会等待,直到主机给从机传了日志才会继续执行。这不会有任何问题。但当主机的Log buffer满了,会发生什么呢?主机必须等到Log buffer中的日志被消耗了,才能继续写。这样的话,连接该服务器的客户端会长时间收不到回复,因为主机一直在等待从机消耗Log buffer中的日志。此外,当主机在这种情况下崩溃的时候,从机需要重放很多主机传来的日志,才能继续接管主机。这也会严重影响用户的体验。
为了解决这个问题,VM-FT在FT协议中的发送信息和确认信息中,额外添加了信息(比如主机和从机执行同一个命令的时间)来计算主机和从机执行同一个命令的时间差。如果时间差过大(比如说超过1s),VM-FT就会通知CPU调度器,让其给主机更少的CPU执行时间。
2.3 其他
VM-FT还有更多的实现细节,本文不再继续展开。如果你感兴趣,可以参考原论文《The Design of a Practical System for Fault-Tolerant Virtual Machines》。
3 VM-FT与GFS的容错性比较
- VM-FT备份的是计算,GFS备份的是存储。
- VM-FT提供了较为严谨的一致性。可以用它为任何已有的网络服务器提供容错。例如,VM-FT 应用于一个已有的邮件服务器,为其提供容错性。
- GFS 只针对简单的服务提供容错性,它的备份策略比 FT 更为高效。例如,GFS 不需要让所有的操作都应用在所有的备份上。