快照原理
之前在介绍 Linux 文件系统的文章中,有提过 ZFS、Btrfs文件系统中,有内置快照的功能,也有提到过其快照的由 CoW 机制实现的。那么这篇文章将带领大家了解快照的原理。
快照技术分类
常见快照的类别有两类:
- 全拷贝快照
- 差分快照
全拷贝快照
拷贝快照是通过镜像技术来实现的,即其同时会写两个磁盘,我们可以理解为磁盘整列技术中的 RAID1。
下面通过一张原理图来介绍全拷贝快照的实现原理。
全拷贝快照特点:
- 空间占用:需要与源盘相同大小的存储空间
- 创建过程:每一次全拷贝快照都需要完全数据同步
读写操作影响:
- 源卷的读操作不受影响
- 源卷的写操作受数据同步的影响
- 创建完成后,快照的读写操作保持最优
差分快照
差分快照又分为以下几种技术:
- CoW (Copy On Write)
- RoW (Redirect On Write)
CoW
CoW (Copy On Write),译为写时复制。这种方式通常也被称为“元数据(源数据指针表)”拷贝。顾名思义,如果快照创建后,试图改写源数据块上的原始数据,首先将原始数据拷贝到新数据块中,然后再进行改写。当还原快照需要引用原始数据时,快照软件将原始数据原有的指针映射到新数据块上。
再来深入的看看 CoW 的过程,CoW 在创建快照时,并不会发生物理的数据拷贝动作,仅是拷贝了原始数据所在的源数据块的物理位置元数据。因此,CoW 快照创建非常快,可以瞬间完成。在创建了快照之后,快照软件会监控跟踪原始数据的变化(即对源数据块的写操作),一旦源数据块中的原始数据被改写,则会将源数据块上的数据拷贝到新数据块中,然后将新数据写入到源数据块中覆盖原始数据。其中所有的源数据块就组成了所谓的源数据卷,而新数据块组成了快照卷。你应该能够看出 CoW 有一个很明显的缺点,就是会降低源数据卷的写性能,因为每次改写新数据,实际上都进行了两次写操作。
不知道你有没有看过前面的一篇文章《认识 EXT2 文件系统》,这篇文章中,描述了文件系统的底层结构。我们知道文件路径与 inode 与文件数据的关系:文件索引 -> inode -> data block。
- 文件索引:记录文件名与 inode 的对应关系
- inode:记录文件数据所在的 data block
- data block:保存文件数据
那么 CoW 的工作原理可以用图来表示,如下:
总结一下,CoW 快照的优缺点:
- 首次修改数据性能下降
- 读数据性能不变
RoW
RoW (Redirect-On-Write),译为写时重定向。RoW 的实现原理与 CoW 非常相似,区别在于「RoW 对原始数据卷的首次写操作,会将新数据重定向到预留的快照卷中」,而 CoW 一般会使用新数据将原始数据覆盖。所以,RoW 快照中的原始数据依旧保留在源数据卷中,并且为了保证快照数据的完整性,在创建快照时,源数据卷状态会由读写变成只读的。
不知道你在使用 Vmware 或 ESXi 虚拟机做快照时,有没有留意到每做一次快照,其实是生成了一下新的快照文件,这个文件最开始是几十 KB 的大小,慢慢的随着虚拟机的运行发生了文件的变动,快照文件大小才逐渐变大。是的, Vmware 快照的实现机制就是 RoW。我们还可以对虚拟机做多个快照,这样就产生了一个快照链,虚拟机磁盘卷始终挂载在快照链的最末端,即虚拟机的写操作全都会落盘到最末端的快照卷中。该特征导致了一个问题,就是如果一共做了 10 次快照,那么在恢复到最新的快照点时,则需要通过合并 10 个快照卷来得到一个完整的最新快照点数据;如果是恢复到第 8 次快找时间点,那么就需要将前 8 次的快照卷合并成为一个完整的快照点数据。从这里可以看出 RoW 的主要缺点是没有一个完整的快照卷,其快照之间的关系是链式的,如果快照层级越多,进行快照恢复时的系统开销会比较大。但 RoW 的优势在于其解决了 CoW 快照写两次的问题,所以就写性能而言,RoW 无疑是优于 CoW 的。
我们也用一张图来表示其原理,如下:
总结一下,RoW 快照的优缺点:
- 修改数据只是指针的重新指向,性能不变
- 因为要追踪其链式关系找到源卷的数据,因此读数据性能下降
快照的应用场景
前面我们介绍了快照的分类,可以分为“全拷贝快照”、“差分快照”。而差分快照的主要实现技术是“CoW”或“RoW”。
那么快照技术适用于哪些场景呢?
全拷贝快照
没见过这么玩的,太昂贵了,也许在一些高端产业上会用到
CoW
大多高端文件系统都实现了 CoW 机制,快照是其卖点之一
很多数据库软件自身也实现了 CoW 机制
RoW
最典型的就是虚拟机的快照了,各虚拟机、虚拟化平台所使用的快照技术一般都基于 RoW 来实现的
UnionFS
UnionFS 本来和快照没有关系,为什么在这里拎出来讲一下呢?因为 UnionFS 技术和 RoW 技术非常的类似,其采用联合挂载的概念将多个层挂载成一个挂载点。
第一层:
/1.txt,内容:111
/2.txt,内容:222
第二层:
/1.txt,内容:112
/3.txt,内容:333
将第一层、第二层联合挂载之后,看到的文件将是:
/1.txt,内容:112
/2.txt,内容:222
/3.txt,内容:333
不知道你发现其中的奥妙了没有?是的,联合挂载之后,整个挂载点就由一层叠加一层来实现的,术语叫遮掩层。其逻辑是:
- 读取上层没有的文件,往下层逐层搜索,一直搜索到最底层,有则读取
- 新增文件,直接体现在上层中,不会往下层读取
- 修改文件,直接体现在上层中,不会往下层读取
- 删除文件,在上层遮掩住,不会往下层读取
Docker 把 UnionFS 的想像力发挥到了容器的镜像。你可以查看这篇文章介绍Linux Namespace上篇用mount namespace 和 chroot 山寨了一镜像。现在当你看过了这个 UnionFS 的技术后,你是不是就明白了,你完全可以用UnionFS 这样的技术做出分层的镜像来。
下图来自 Docker 的官方文档 Layer,其很好的展示了 Docker 用 UnionFS 搭建的分层镜像。
关于 docker 的分层镜像,除了 aufs,docker 还支持 btrfs, devicemapper 和 vfs,你可以使用 -s 或 --storage-driver= 选项来指定相关的镜像存储。在 Ubuntu 14.04 下,docker 默认 Ubuntu 的 aufs(在CentOS7下,早期用的是devicemapper,现在用的是 Overlay File System)。