数据是一个企业非常重要的一种资产,其重要性不亚于企业的工厂或者大厦。因此,如何保证企业数据的安全就显得非常重要了。在美国911事件之后,很多公司因为数据丢失而倒闭, 然后Cantor Fitzgerald公司由于使用了EMC的数据保护技术从而快速恢复了业务。
当前勒索病毒非常猖獗,有一些设计公司因为病毒感染,导致整个公司的数据被黑客加密。这种情况的出现,小则损失大量的资金,大则可能导致公司倒闭。如果企业的存储系统有数据保护的功能,那么可以非常容易地恢复数据,从而避免被勒索。
在企业级存储系统中有很多保护数据的特性,常见的特性比如快照,克隆和远程复制等等。除此之外,还有数据备份和归档等服务。这样特性从不同的角度实现了对用户数据的保护,从而保证在生产数据出现丢失的情况下可以通过“备份”的数据进行恢复,从而保证数据的安全性。
快照是存储系统中的一种数据保护技术,主要是实现数据的逻辑保护。所谓逻辑保护,就是当数据出现误删除或者病毒等原因导致数据破坏的情况,通过快照技术,可以将数据恢复到某一个时间点的数据。相对于物理保护,快照无法应对诸如硬盘故障、控制器被砸或者数据中心被水淹的情况。
从名字上可以猜出来,快照就好像给数据拍了一个照片,就好像我们日常生活中给人拍照片类似。以生活中拍照片为例,比如给你家娃娃拍了他2岁的照片,等他到5岁的时候,样子变化很大。这个时候你想看看他2岁的时候长什么样,那你就可以拿出他2岁的照片看看。
对于存储中的快照技术也是类似,在某个时间点,你给某个磁盘(或者存储中的LUN)打一个快照,相当于让存储系统给这个磁盘拍了一个照片。当之后的使用过程中发生了意外,比如病毒把数据搞坏了,或者误删除了文件等等。这个时候你就可以通过快照知道原来的数据是什么样的,这样数据就可以找回来了。
快照只能实现对数据的崩溃一致性保护,而无法实现事务一致性保护。这个事务主要是针对数据库系统的事务。因为数据库的事务操作可能涉及多个IO,而打快照的瞬间是无法保证所有相关IO都完成的。这个可能不太容易理解,我们举一个例子。比如用户在拷贝一个图片文件的时候(拷贝到50%)做了一个快照,那么由于某种原因需要通过该快照恢复时,只能恢复该文件50%的数据,后面的数据是找不到的。
快照的实现原理
快照可以在文件系统或者块设备层面实现,我们以块存储为例进行相关的介绍。这里的块设备可以是主机端的硬盘或者逻辑卷或者存储端的LUN。从用户的角度块设备其实就是一个线性空间,可以理解为一个很大的数组,比如512G或者4TB等。
早些时候由于没有精简卷技术,快照通常采用镜像分离 (split mirror)技术实现。当一个卷被创建时,底层实际会创建多个卷,写数据会被复制到所有卷上,类似RAID1(如图上半部分)。当创建快照的时候,将其中一个卷从关系中剥离出来,此后,原始卷在写入数据时被剥离的卷不再被写入新数据,保持剥离时的状态,就是一个快照(如图下半部分)。
精简卷出现后,快照技术发生了很大的变化。我们知道精简卷虽然在用户层面呈现的是一个线性空间,但是在存储系统中是通过一棵树管理的离散的空间。卷的空间被划分为固定大小的逻辑块(如4KB),所有4KB的数据通过一棵树管理起来。此时,主机端看到的硬盘与存储端逻辑卷与存储池之间的关系如下图所示。
在上图中,逻辑卷中的数据以逻辑块为粒度存储在存储系统中的存储池中。如前文所说,这种映射关系通常通过一棵树建立,典型的如ZFS的逻辑卷就是这样。逻辑卷在存储系统中通常也就是一个数据结构,快照也大致如此。
CoW与RoW
基于精简卷,快照的实现方式有了很大的变化。由于逻辑卷入口为一个数据结构,而数据通过一棵树映射到存储池中。所以,当我们打快照的时候,其实就是创建一个新的数据结构,并建立映射关系,具体如下图所示。可以看出,此时快照与原始卷有相同的映射关系。
问题是,如果原始卷要写数据呢?如下图所示的数据被写入,由于快照也指向这块数据,如何保证从快照视角保证数据不发生变化呢?目前有两种常用的方式,一种是写时重定向(Redirect-On-Write),简称RoW;一种是写时复制(Copy on Write),简称CoW。
对于RoW方式,当原始卷写入新数据的时候,新写的数据被重新写入新的位置,映射关系发生变化。对于快照而言,对应位置的数据保持原有的映射关系不变化。因此,通过这种方式可以保证快照的数据依然是打快照时刻的数据。
对于CoW方式,当原始卷写入新数据的时候,新写入的数据会在原位置进行覆盖。而原来的数据需要被拷贝到一个新的位置。同时,需要变更快照的映射关系,让快照指向这个新位置的数据。所以,虽然原始位置的数据发生了变化,但是源数据被拷贝到了新的位置,从快照的角度来看,数据依然是打快照时刻的数据。
上述两种技术,虽然原理上非常简单,在实现的时候还是有很多细节需要考虑。我们可以以开源项目为切入点了解一下快照的更多实现细节。开源存储系统中快照的实现非常普遍,如ZFS和Linux内核存储栈都有快照的具体实现。
快照实例详解
我们先介绍一下ZFS文件系统中快照相关的内容。在ZFS当中创建一个快照是非常简单的,我们只需要执行如下命令就可以创建一个快照。
由于ZFS在卷数据的更新本身就是CoW的,所以ZFS的快照也是CoW的,而且实现起来还非常简单。对于ZFS的卷,如果有一个数据块需要更新,则整个路径从下往上都会分配一个新的数据块,并将数据持久化到新的位置。如下图所示,数字是数据块分配的顺序,最终新树是由新的数据块和老的数据块构成。
由于新写的数据以及整个路径都会新分配存储空间,所以原始的路径依然存在,如下图所示。我们知道ZFS中通过一个dnode来表示一个逻辑卷,因此我们只需要新建一个新的dnode,并用这个dnode索引原来的根节点就可以检索到打快照时刻的所有数据。
问题在于快照和原始卷存在共享数据,当删除快照的时候如何释放空间呢?为了能够正确地释放空间,ZFS在指向下级的指针中包含一个下级的生成时间(Birth Time)。在删除快照的时候,只有生成时间大于前一个快照创建时间的数据块才可以被删除。
如下图所示,快照1的创建时间是30,快照2的创建时间是40,两个快照之间所有的数据改动的时间必然都在30与40之间,图中绿色方块。而30及小于30的数据块必然被快照1所引用。
对于创建时间等于快照2创建时间的数据块,除了被快照2引用外,还有可能被原始卷引用,因此需要遍历原始卷,确保没有引用相应的数据块。如下图最下一层右侧的数据块,在删除快照2的时候是不可以被释放的。
所以,经过2次遍历之后就可以确定哪些数据块可以被删除了,此时这些数据块会被放入待删除列表中(Dead List)中,后续ZFS会根据该列表释放空间。
领取专属 10元无门槛券
私享最新 技术干货