接上文《架构设计:系统存储(1)——块存储方案(1)》
本小节我们要解决一个关键问题:既然机械硬盘和固态硬盘从工作原理、制作工艺、技术规范等多个方面都完全不一样,那为什么无论硬件层是使用机械硬盘还是固态硬盘操作系统却都可以进行识别,并在其上进行数据读写呢?
这个问题中,计算机系统不同层次对数据操作最小单位的定义不一致都还是一个小问题:虽然机械硬盘上数据操作单元为512字节、固态硬盘上数据操作单元为4KB、操作系统层面定义的数据操作单元可能是1KB\2KB\4KB\8KB等等。但是只要这些层次上的文件起始地址都是固定的,则各层的地址对应关系就可以找到的。也就是说操作系统上的地址X可以映射到机械硬盘的地址Y又或者映射到固态硬盘的地址Z,只不过存储小文件时的真实可用空间可能产生误差。
但是这里有一个固态硬盘上的操作规则会引起比较大的问题,这就是固态硬盘对数据的删除操作:固态硬盘在进行数据删除时是按照“块”单位进行的,一个“块”包含128个或者256个Flash Page。当进行删除操作时,SSD主控芯片会首先将这个块中还“有效”的数据移动到属于其它“块”的另一些Flash Page中,然后再进行“无效”数据的清理。也就是说以前操作系统通过地址X的可以读取的文件数据,现在通过地址X就可能读取不到了。那么这些固态硬盘的底层操作过程对于操作系统来说应该是完全透明的,否则操作系统就不能将固态硬盘当成机械硬盘进行操作。
这就是说,对于操作系统来说以前使用地址X进行存储的文件,无论什么是否都能够再通过地址X读取到。这里提到了两种地址:一种是操作系统读写文件的地址——称为逻辑地址;另一种是固态硬盘进行文件操作的真实地址——称为物理地址。
FTL(Flash translation layer)闪存地址转换是SSD固态硬盘控制芯片需要负责的主要工作之一,FTL的主要作用就是记录物理地址和逻辑地址的转换关系,FTL的核心是一张物理地址和逻辑地址的映射表,这张映射表存储在固态硬盘一个专门的SRAM/DRAM芯片上或者若干独立的NAND Flash Page 上。正是SSD固态硬盘的控制芯片有这样一个转换过程,操作系统才能将固态硬盘当做机械硬盘进行操作,并且SSD固态硬盘主控制芯片上FTL算法的性能直接影响着整个SSD固态硬盘的性能。请看下图的FTL转换示例:
操作系统对磁盘读写操作的最小单位为“簇”(EXT文件系统称为block size)。以NTFS文件系统为例,默认的“簇”大小为4K(当然您可以选择更大的“簇”大小,这样会浪费更多的存储空间,但是可以加快读写性能)。即使一个文件的大小不到4K,也会占用一个“簇”的大小。如果一个文件为210KB,那么理论上就需要占用53个“簇”空间(4KB),或者需要占用105个“簇”空间(2KB)。
当一个210KB大小文件的写请求从操作系统层传来时,都以逻辑地址进行描述。当SSD固态硬盘主控芯片收到这个文件的写请求时,会到FTL映射表中寻找53个空闲的Flash Page来存储这些数据,并将物理位置和逻辑位置的映射关系记录到FTL映射表中;当一个210KB大小文件的读请求从操作系统传来时,SSD固态硬盘控制芯片会首先在FTL映射表中寻找逻辑为止对应的若干物理位置,以便知晓到固态硬盘的哪些Flash Page去读取数据。
还需要注意,在进行数据写操作时,如果没有寻找到足够的空闲的Flash Page位置那么有两种可能:第一种可能是,在SSD固态硬盘上确实已经没有210KB的空间了,这时操作系统就会收到磁盘空间已满的信息;另一种情况是有部分空间被“无效”数据占用(这些“无效”数据来是前被操作系统删除的数据),这时固态硬盘就要进行无效数据清理。SSD固态硬盘的数据清理是将若干Flash Page的区域全部清理,称为块。清理操作过程已经在上文中大致介绍过。这也是为什么固体硬盘在使用一段时间后(特别是存储空间被占用满后),固态硬盘的性能会出现明显下降的原因。
最后需要注意,实际上由操作系统传来的数据和操作请求也不是直接就发送到SSD主控芯片了,而是需要经过软件和硬件的多层传递。在硬件层面上来说,数据一般需要通过主板上的南桥芯片(在经过磁盘阵列控制芯片)才能传送到SSD固态硬盘的外部接口(例如SATA3.0、USB3.0等),最后再达SSD主控芯片。
单块硬盘进行数据存储可能会存在以下问题:
硬盘容量有限制,当容量不足时不能进行硬件扩容。现在磁盘技术在磁盘容量上已经有了长足的发展,目前(2016)机械硬盘的主流容量已经达到6TB,固态硬盘的主流容量也达到512GB。但是单块硬盘始终都存在较严重的容量扩充问题,除非读者在扩容时手动迁移数据。
数据可靠性性问题。单块硬盘不存在任何备份机制,虽然现在有很多扇区检测软件可以帮助开发人员/运维人员提前发现硬盘损坏的磁道,但是都不能保证99.99%的运行可靠性。一旦硬盘由于各种原因损坏(电压不稳、磁头位移等),存储在其上的数据就可能永久丢失。
读写性能瓶颈。这个问题在SSD固态硬盘上还不太明显,目前主流的固态硬盘的外部传输速度可达到550MB/S,这个速度基本上达到了SATA 3/USB 3.0接口规范的理论峰值。但这个问题对于机械硬盘来说却很明显了,由于机械硬盘的读写性能受到磁头数量、盘片转速、盘片工艺等因素的影响,所以机械硬盘的读写性能一直没有一个质的飞跃。如果将单个硬盘应用在生产系统上,那么磁盘读写性能无疑将会整个系统的性能瓶颈。另外SATA 3结构理论6Gbps的传输带宽必要时也需要找到替代方案。
为了解决以上这些问题,硬件工程师将多个硬盘按照不同的规则组合在一起形成各种集群化的数据存储结构,这些存储结构被称为磁盘阵列(Redundant Arrays of Independent Disks,RAID)。磁盘阵列解决以上这些问题的基本思路有:
目前磁盘阵列结构有多种,包括 RAID 0、RAID 1、RAID 2、RAID 3、RAID 4、RAID 5、RAID 10/01、RAID 50等。其中RAID 2、RAID 3、RAID 4这三种阵列结构常用于阵列研究,生产环境中常使用的阵列结构为RAID 0、RAID 1、RAID 5和RAID 10/01。下面我们就对这些磁盘阵列结构逐一进行介绍。
RAID 0阵列结构是所有阵列结构中读写性能最好的,也是所有阵列结构中实现思路最简单的:
RAID 0阵列结构没有数据冗余机制和数据恢复机制,它至少需要两个硬盘进行构造。整个RAID 0阵列结构就是将参与RAID 0阵列构建的所有硬盘进行容量累加,从而形成一个更大的、对上层操作系统统一的存储容量。所以RAID 0阵列的存储容量就是这些硬盘的容量进行累加。
当需要写入的数据到达阵列控制器,后者会向其下的硬盘设备分发这些数据。这样原来可能只由一个硬盘承担的读写压力就会被分担到多个硬盘上,最终提高了整个阵列的读写性能。RAID 0阵列结构存储速度的优势非常明显,且参与构造阵列的磁盘数量越多阵列速度越快(峰值速度最终会受到总线、外部接口规范、控制芯片制造工艺等因素的限制)。但是RAID 0阵列结构的缺点也很明显:由于阵列结构没有容错机制或者数据恢复机制,当阵列中的一个或者多个磁盘发生故障时,整个阵列结构就会崩溃并且不能恢复。所以在实际应用中,只有那些单位价值不高且每天又需要大量存储的数据才会使用RAID 0阵列结构进行存储,例如日志文件数据。
RAID 1阵列结构又被称为磁盘镜像阵列或者磁盘冗余阵列。它的构造特点是阵列结构中的每一个磁盘互为镜像:
当有外部数据需要存储时,RAID 1阵列控制器将会首先把这个数据做成N个副本(N的数量和阵列结构中物理磁盘的数量相等),实际上镜像副本的单位为扇区或者Flash Page。这些副本会分别存储到阵列结构的各个磁盘中。在进行数据读取时,RAID 1 阵列结构中的某一块磁盘将会作为主要的数据读取源头,当这个源头出现吞吐量瓶颈时,RAID 1阵列控制器会主动到其它镜像磁盘读取数据。所以RAID 1阵列的数据读取性能还是要比单个磁盘的性能要好,但是写入性能却差了很多。
从以上介绍可以看出,RAID 1阵列结构设计之初的主要目的并不是提高存储设备的读写性能,而是保证高价值数据的存储可靠性。由于RAID 1阵列结构中需要保证每个磁盘的镜像数据完全一致,所以它还要求参与RAID 1阵列结构的每一个磁盘的容量必须相同,否则RAID 1阵列结构会以最小的那个磁盘容量为自己的标准容量。
RAID 0和RAID 1都有自己的优缺点,并且这些特点都很突出:RAID 0虽然速度快但是没有任何数据保障措施,所以一味地快意义并不大;RAID 1虽然保证了数据的可靠性,但是却牺牲了大量空间和读写速度。所以以上两种阵列结构特别是RAID 0,在企业级/工业级环境中使用的情况还是比较少。那么有没有一种阵列结构在融合了RAID 0和RAID1两者优点的同时又避免了各自的缺点呢?
答案是:有的!RAID10和RAID01两种阵列结构就是为了实现RAID 0和RAID 1的融合而被设计的。在RAID10结构中,它首先将参与阵列结构组建的磁盘进行分组,形成若干组独立的RAID 1阵列结构,然后再将这些独立的RAID 1阵列结构形成RAID 0结构,如下图所示:
上图中有四块硬盘参与RAID 10阵列结构的组建,四块硬盘是组建RAID 10阵列结构的最小要求(实际上两块也行,但是那样的RAID 10没有任何意义)。它们首先被两两分组形成两个独立的RAID 1结构,这也意味着这些硬盘的容量最好是一样的,否则每组RAID 1结构会基于容量最小的那块硬盘确认自己的容量。接着独立工作的两组RAID 1再组成RAID 0阵列结构。
假设参与RAID 10构建的硬盘大小都为6TB,则两组独立的RAID 1阵列结构的容量分别为12TB,最终整个RAID 10阵列结构的存储容量为12TB。可以看到RAID10阵列结构的存储容量和独立磁盘的大小、分组数量有关。我们可以得到以下的计算公式:
RAID 10总容量 = N / G * 单个硬盘的存储容量
这个公式假设的前提是参与RAID 10构建的每个硬盘的存储容量都相同。其中N表示参与RAID 10构建的硬盘总数,G代表RAID 10下磁盘映射的分组数量(RAID 1分组数量)。例如,总共12块硬盘参与RAID 10构建,每个硬盘的大小为6TB,且分为三组RAID 1,那么这样组建的RAID 10阵列结构的存储容量为24TB;如果同样的情况下,这些硬盘被分为四组RAID 1,那么组建的RAID 10阵列结构的存储容量就为18TB。
可见RIAD 10通过集成更多硬盘的思路,将RAID 0阵列和RAID 1阵列的特点进行了融合,在保证数据存储可靠性的基础上提高了阵列的整体存储性能。RAID 10被广泛应用在各种计算场景中,市场上从几千到几百万的阵列设备都提供对RAID 10磁盘阵列结构的支持。RAID 10磁盘阵列的总读写速度会受到控制芯片的影响,所以几千和几百万的磁盘阵列设备实际读写性能是完全不一样的 。
另外还有一种和RAID 10阵列结构相似的阵列结构:RAID 01(或称为RAID 0 + 1),它们的构造区别是,后者首先将若干磁盘以RAID 0的方式进行组织,然后再分组成多个独立的RAID 1结构:
RAID 5阵列结构和RAID 10/01阵列结构在实际生产环境中都经常被使用,前者的应用更为广泛:虽然速度上RAID 5没有RAID 10/01阵列结构快,但是RAID 5阵列控制芯片的成本却低很多。RAID 5阵列基于奇偶校验原理,它的算法核心是异或运算(XOR)。异或运算是各位读者在大学离散数学课程中学习过的一种基本二进制运算,其运算关系如下表所述(以下表格的计算因子只有两个,目的是让读者回忆起来):
A值 | B值 | 运算结果 |
---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
接着我们可以再假设计算因子为N,根据异或运算的特点,我们可以在已知结果和N-1个原始计算因子的前提下,还原出未知的那个计算因子。请看下面示例的计算过程(N == 4):
...... 1 ^ 1 ^ 1 ^ ? = 1 -----> ? = 0 1 ^ 1 ^ 1 ^ ? = 0 -----> ? = 1 1 ^ 0 ^ 1 ^ ? = 1 -----> ? = 1 1 ^ 0 ^ 1 ^ ? = 0 -----> ? = 0 0 ^ 0 ^ 1 ^ ? = 1 -----> ? = 0 0 ^ 0 ^ 1 ^ ? = 0 -----> ? = 1 0 ^ 0 ^ 0 ^ ? = 1 -----> ? = 1 0 ^ 0 ^ 0 ^ ? = 0 -----> ? = 0 ......
有了以上的理论基础,我们就可以将它应用到实际的块存储工作中。试想一下如果将以上异或运算的每个计算因子扩展成磁盘上的一个数据扇区并针对多个扇区进行异或运算并将计算结果存储下来。那么是否可以在某一个数据扇区出现问题时恢复数据呢?答案是肯定的,请看如下扇区校验实例:
在以上四个扇区的校验示例中,它们分属四个不同的磁盘设备,其中三个扇区存储的是数据,最后一个扇区存储的是异或运算后的校验码。在上一篇文章中我们已经介绍过一个扇区存储的数据量为512字节。当某个数据扇区出现故障时,基于校验扇区的信息和正常状态的数据扇区的信息,RAID 5磁盘阵列可以将发生故障的扇区恢复出来;当某个校验扇区的信息出现故障时,RAID 5磁盘阵列还可以重新进行校验。也就是说RAID 5阵列结构同一时间内只允许有一块硬盘出现故障,出现故障的硬盘需要立即进行更换。
如果还未来得及更换故障硬盘,另一块硬盘又出现了故障,那么对整个RAID 5阵列就是毁灭性的——因为无法通过异或计算同时恢复两个计算因子。当更换故障硬盘后,RAID5阵列控制器将会自动对数据进行重新校验,恢复数据。为了在可靠性和读写性能上找到平衡,RAID 5阵列结构会将存储同一个文件的若干扇区分布在阵列下的若干磁盘上(设阵列中磁盘总数为N,则文件数据扇区分布于N-1个磁盘上),并将这些扇区的校验信息存储在最后剩余的一块磁盘上;RAID 5阵列结构中,校验信息也并不是全部存储在一块相同的磁盘上,而是均匀分布在每一块磁盘中,这样做的目的是为了尽可能快的完成数据恢复过程。
============================
(接下文)