1.磁盘结构简介
如上图,磁盘在物理上是相对简单的。磁盘的每一个盘片是扁平的圆盘,它的两个表面都覆盖着磁性物质,信息就记录在表面上。盘片的表面从逻辑上划分为磁道,磁道有划分扇区。扇区是从磁盘读出和写入信息的最小单位。扇区大小一般是512字节。盘片在读写头下方转动时,通过反转磁性物质磁化的方向将二进制信息磁化存储到扇区中。每一个盘片的每一面都有一个读写头,读写头可以在盘片上移动来访问不同的磁道,一个磁盘的所有盘片上的读写头统一安装在磁盘臂上,并且是一起移动的。所有盘片的第i条磁道合在一起称为第i个柱面。磁盘的读取是以扇区(一般为512B)为最小单位的,而操作系统则是以簇为单位与磁盘进行交互。此外,需要注意的是许多文件系统一次性读取或写入是以4KB或更多的字节来进行的,但驱动器制造厂商唯一保证的是写入磁盘时单个512个字节的写入是原子性的。
磁盘通过一个高速互连通道链接到计算机系统,今天常用的将计算机与磁盘连接在一起的接口有:
1.SATA(Serial ATA)即串行ATA,ATA的旧版本称为PATA或并行ATA或IDE。
2.SCSI(Small-Computer-System Interconnect)
3.SAS(Serial attached SCSI)
4.FCI(Fibre Channel Interface)
此外,便携式外部磁盘系统经常使用USB接口或IEEE1394火线接口。
磁盘的性能指标主要包括:容量、访问时间、数据传输速率、可靠性,接下来我们一一介绍。
2.磁盘性能分析
2.1容量
容量即磁盘所能存储二进制位数的多少,磁盘容量的计算公式可表示为:硬盘容量大小=磁头数×柱面数×扇区数×每扇区字节数
2.2访问时间与数据传输速率
再具体探讨访问时间之前先介绍一下操作系统与IO设备交互常见的三种方式:
一个简单的IO设备模型如下图:
如上图,一个IO设备可以简单的理解为包含两部分重要组件的设备。第一部分是想系统其他部分展现的硬件接口,让操作系统与其进行交互。第二部分是它的内部结构,这部分包含设备相关的特定实现,负责具体实现设备展示给系统的抽象接口。
设备接口的几个寄存器的功能如下:
状态寄存器:可以读取并查看设备的当前状态。
命令寄存器:用于通知设备执行某个具体的任务。
数据寄存器:将数据传给设备或从设备接收数据。
操作系统通过与这些寄存器进行交互来控制设备。
1.轮询
我们用如下协议来描述操作系统与设备的典型交互过程:
while(STATUS == BUSY)
;//wait until device is not busy
Write data to DATA register
Write command to COMMAND register
//Doing so starts the device and executes the command
While(STATUS == BUSY)
//wait until device is done with your request
该协议包含4步:
1.操作系统反复读取状态寄存器,等待设备进入可以接收命令的就绪状态。称为轮询设备
2.操作系统下发数据到数据寄存器
3.操作系统将命令写入命令寄存器
4.操作系统再次轮询设备,等待并判断设备是否执行完成命令(得到一个成功或失败的错误码)
这个协议存在一个问题,轮询过程比较低效,在等待设备执行完成命令时浪费大量CPU时间,如果操作系统此时可以切换执行下一个就绪进程,那么可以显著提升CPU利用率。
2.中断
在这种协议下,CPU不在需要不断的轮询设备,而是向设备发出一个请求,然后就可以让对应的进程进入睡眠状态,切换至其它任务继续执行。当设备完成了自身从操作,会抛出一个硬件中断,引发CPU跳转执行操作系统预先定义好的中断处理程序,中断处理程序会结束之前的请求(比如设备读取到了数据或者错误码)并唤醒等待IO的进程继续执行。中断允许IO和计算重叠,因此提高了CPU的利用率。
但是,使用中断并不一定是最佳方案。假如有一个性能非常高的设备,它处理请求很快,通常在CPU第一次轮询时就可以返回结果,在这种情况下使用中断反而会导致系统变慢(上下文切换和中断处理)。因此在设备非常快的情况下使用轮询还是比较好的,如果设备速度不较慢采用中断是比较好的。此外,如果设备性能时快时慢,可以考虑使用混合策略,先轮询一段时间,如果设备没有完成操作,此时再使用中断。
另外一个典型的场景是网络。如果对于每一个网络包都发生一次中断,那么可能会导致操作系统不断的处理中断而无法处理用户层的请求,这时一种比较好的方法就是使用轮询。常见的网卡工作方式中有一种方式是:先使用中断等待网络包的到来,当收到第一个网络包的时候便采用轮询的方式,直到不再接受网络包后再变为中断等待。
对于中断而言,另一个优化是合并。设备在抛出中断之前先等待一小段时间,在等待的过程中很有可能其他请求也完成,因此多个中断可以合并为一次中断抛出,从而降低中断处理的代价。但是太长的等待时间会增加请求的延迟时间。
3.DMA
在前两种协议下,CPU都参与了数据的移动过程,这种IO方式我们成为PIO(progranned I/O)。使用PIO,CPU的时间会浪费在向设备传输数据或者从设备传出数据的过程中,因此可以采用一种办法让CPU从数据传输的过程中脱离出来,这种方案就是DMA传输方式。
DMA(Direct Memory Access):DMA引擎是系统中的一个特殊的设备,他可以协调内存和设备间的数据传递,不需要CPU的介入。
DMA工作过程如下,为了能够将数据传送给设备,操作系统会通过编程告诉DMA驱动器数据在内存的位置,要拷贝的大小以及要拷贝到哪个设备,此后操作系统就可以处理其他请求了。DMA完成任务后,DMA控制器会抛出一个中断告诉操作系统已经完成数据传输。
了解了数据访问的方式后,再来看访问时间的问题。
通常来讲访问磁盘时都需要启动磁臂,移动磁头进行对磁道的定位,然后等待期待的扇区转动到磁头下即可读取数据。
因此,访问时间可由三部分组成:寻道时间、旋转延迟、传输时间
接下来我们来计算随机IO和顺序IO两种不同的工作负载下的访问时间。
随机IO:指向磁盘的随机位置发出小的(4KB)读取请求。
顺序IO:只是从磁盘读取连续大量的扇区,不会跳过某一个扇区。
先给出两个现代磁盘的参数:
如上图,第一个是高性能SCSI驱动器,第二个是为容量的而生的驱动器,这也代表了磁盘驱动器市场的两个重要部分。
**Cheetah随机IO时间(以4KB为例)😗*T寻道 = 4ms,T旋转 = 2ms,T传输=30us
旋转延迟时间由RPM得到,15000RPM=250RPS(每秒转速),因此单次旋转需要4ms,就平均而言需要2ms。
因此我们得到Cheetah的TI/O 大致为6ms,IO的速率为4KB/6ms=0.66MB/s.同样可以得到Barracuda的TI/O 大致为13.2ms,速率为0.31MB/s
顺序IO(100MB为例):在此期间只有一次寻道和旋转,Cheetah的TI/O 大致为800ms,Barracuda的TI/O 大致为800ms,而I/O速率接近125MB/s、105MB/s。
由上面的计算可以看出,随机IO与顺序IO之间的速率差别极大,因此合理安排访问顺序能一定程度上降低磁盘访问时间。
补充:平均寻道时间大约为完整寻道时间的三分之一,证明不会。。。
到这里已经可以发现,磁盘的访问速度和内存、CPU的速度比起来太慢了,那么如何改善呢?
1.缓冲
2.预读:对顺序IO很有效,对随机IO表现不佳。
3.调度算法:通常使用电梯调度算法
4.合理组织文件存储结构
5.非易失性写缓冲区,更新密集的应用高度依赖于磁盘的写操作速度,此时可以使用非易失性随机访问存储器加速写磁盘的操作。
6.日志磁盘,对日志磁盘的所有写操作都是顺序的,从根本上消除了寻道时间。支持日志磁盘的文件系统成为日志文件系统(但日志文件系统可以在没有单独的日志磁盘的情况下实现,此时只需将数据和日志放在一张磁盘上,但会降低性能。)同时日志文件系统在系统重启时减少执行文件系统一致性检验的时间,从而允许快速重启。
2.3可靠性
引起磁盘不可靠的因素主要有以下几种:
1.电源故障
对于这类错误,可以增加多个电源或备用电池的方式来解决。
2.自然灾害
可以将数据进行冗余存储分散在不同地域,或者对数据进行定时备份等。
3.潜在扇区错误
此类错误主要表现形式为数据位不可读(比如磁头接触到表面),内容不正确(比如一些射线引起位反转),可以使用磁盘内部纠错码来进行检测错误,并且如果有足够信息来进行修复的话会进行修复,否则返回错误。
此类错误中有一个指的关注的问题是:如果冗余机制采用的是镜像RAID,当全盘故障和潜在扇区错误同时发生时RAID4/5会产生一个问题。当整个磁盘发生故障时,RAID会尝试读取奇偶校验组中的所有其他盘,并重新计算缺失值,来重建磁盘,但是在重建期间,任何一个其他磁盘上发生潜在扇区错误的话,就会发生无法完成重建的问题。这个问题的一个解决思路是增加额外的冗余度,例如使用两个奇偶校验磁盘。但这会引起时间(每个条带同时维护两个奇偶校验块)和空间(额外存放一个奇偶校验块)的增加。对于时间的影响,NetApp WAFL文件系统的日志结构特性降低了该成本。
4.块讹误
该类错误磁盘本身无法检测到,比如,有缺陷的磁盘固件可能会将块写入错误的位置,此时纠错码指示块内容正确,但是站在用户的角度,稍后会收到错误的数据,或者是总线产生错误等。
检测此类错误使用的技术主要是校验和
常见的校验和函数有:异或、加法、Fletcher、CRC等。
异或比较简单,但是有局限性,思考如果每个校验和单元内相同位置的两个位发生变化此时校验和将不会检验到错误。
Fletcher和CRC几乎一样强,可以检测所有单比特错误、所有双比特错误和大部分突发错误。
但是无论使用哪种校验和函数都存在一个问题,两个具有不相同内容的数据块可能具有相同的校验和,这个问题称为碰撞。这和Hash函数是类似的,将很大的东西产生很小的摘要是存在一定几率发生碰撞的。因此在选择校验和函数时,应当在保持易于计算的同时最小化碰撞发生的概率。
确定了校验和函数后,客户端读取数据时先计算校验和,然后将计算的校验和读取的校验和比较即可。
但是有一个新的问题,如果发生写入简单的校验和可能不那么有用。如果数据在写入磁盘时没有错误但是写入的位置出错,此时上面的校验和就无能为力了。为了解决这一问题,可以在校验和中加入更多的冗余信息,即添加物理标识符(磁盘号和扇区号)。
最后还有一个问题,丢失写入。当设备通知上层写入已经完成,但事实上它并未持久化,此时就会发生这种问题。磁盘上保留的是旧的内容,新的写入丢失,错误也无法检测。此时应该怎么处理呢?一种经典的方法是执行写入验证或写入后读取。某些系统可以在系统的其他位置添加校验和,检测丢失的写入。例如Sun的Zettabyte文件系统在文件系统的每个inode和间接块中包含文件每个块的校验和。此时,即是数据块本身写入丢失,inode内的校验和也不会与旧数据匹配,只有当inode和数据同时丢失时才会失败,但是发生这种情况的概率很小。
经过以上的讨论,你可能会有疑问,这些校验和什么时候检查,仅仅在读取数据块的时候吗?
但是大多数数据是很少被访问的,因此会保持未检查状态,同时数据位的衰减可能最终会导致丢失特定数据的所有副本,许多系统使用磁盘擦净来解决这个问题。通过定期读取系统的每个块,并检查校验和是否仍然有效,磁盘系统可以减少某个数据项的所有副本都被破坏的可能性,典型的系统每晚或每周扫描一次。
RAID简介
单一磁盘的缺点很明显,多个IO操作无法并发进行,数据冗余不易实现,磁盘容量有限,这些问题都表名期望出现一个具有更快的IO速度、更大存储容量、更可靠的存储的系统的出现,RAID就是这样一个磁盘系统。
RAID(Redundant Array Of Inexpensive Disks)廉价磁盘冗余阵列:使用多个磁盘旨在构建一个更快、更大、更可靠的系统。RAID得I本意是Inexpensive,但随着磁盘造价越来越低,现在I更多指代的是Independent。此时使用RAID时经济因素就不是主要的了。
另一方面,RAID便于管理和操作。从外部来看RAID看起来像一个磁盘(包含一组可以读取或写入的块)。在内部RAID其实很复杂,他由多个磁盘、内存(易失性和非易失性)以及一个或多个处理器。硬件RAID看起来更像一个计算机系统,但它是为了管理一组磁盘。RAID对使用它们的系统来说是透明的,这意味着不需要更换一行代码,就可以代替磁盘继续运行。
RAID具体实现时具有不同的实现方案,称为RAID不同的级别,主要分为6个级别,它们其中有些是过时的,因此接下来只介绍其中的4个级别(RAID0、RAID1、RAID4、RAID5)。
再具体介绍之前,我们先假设RAID中的每个磁盘只处于两种状态:工作或故障状态,处于工作状态下所有块都可以读取或写入,处于故障状态时所有块都无法正常工作。此外,接下来会从容量、可靠性、性能三个方面来比较RAID不同级别的性能。
对于性能,我们考虑两种不同的指标:单请求延迟和稳态吞吐量(顺序和随机工作负载下)
RAID0级:RAID0级没有任何冗余,可作为性能和容量的上限。
条带:我们将同一行中的块称为条带,比如上图中的0、1、2、3。
上图中我们将两个逻辑上连续的块放在一个磁盘中,此时大块大小即为2.
RAID映射:将逻辑块映射到物理磁盘上的具体位置(磁盘号和偏移量)。
对于不同的RAID级别RAID映射可能不一样,以RAID-0为例:假定逻辑地址为A,大块大小为1.
磁盘号 = A % 磁盘数
偏移量 = A / 磁盘数
接下来讨论大块大小对RAID的影响。大块大小较小意味着文件会跨越多个磁盘进行条带化,因此增加了对单个文件的读取和写入的并行性。但是跨多个磁盘的访问快的定位时间会增加(由所有驱动器上请求最大定位时间决定。)。而大块大小较大减少了并行性,因此依赖多个并发请求来提升吞吐量,但是减少了定位时间。因此“最佳”大块大小是很难确定的,接下来的讨论都假定大块大小为1(4KB),注意接下来的内容不受大块大小的影响。
容量:给定N个磁盘,RAID-0提供N个磁盘的容量。
可靠性:由于不存在任何冗余,因此可靠性极差。
性能:我们重点比较性能指标。
在本篇文章前面,我们已经了解了随机IO和顺序IO的传输速率差距非常大,这里我们再计算一遍。假设某磁盘参数如下:平均寻道时间为7ms,平均旋转延迟3ms,磁盘传输速率50MB/s,比较在平均大小为10MB的连续传输和平均大小为10KB的随机传输两种工作负载下传输速率S和R。
对于S:T = 7ms + 3ms + 10MB/(50MB/s) = 210ms S = 10MB / 210ms = 47.62MB/s
对于R:T = 7ms + 3ms + 10KB/(50MB/s) = 10.195ms R = 10KB / 10.195ms = 0.981MB/s
可见S << R。
对于RAID-0来说,单块请求的延迟和单个磁盘的延迟几乎相同。从稳态吞吐量来看,吞吐量 = 磁盘数量 * 单个磁盘的IO速率,由于顺序IO和随机IO不同因此有两个不同的速率,即N(磁盘数量) * s,N *R。RAID-0给出了一个上限,便于标记其它RAID的性能。
RAID1级
RAID-1级也称为镜像级别,即对每个块都进行单独的备份,典型的对每个逻辑块RAID都保留两个物理副本。如下
上面的这种安排方法称为RAID-10(1+0),他先使用镜像然后在其上进行条带化。另一种安排方法是RAID-01,它先进行条带化,然后在进行镜像备份。
从镜像陈列读取数据块时,RAID可以读取任一副本,但是在写入数据块时,RAID必须更新两个副本的数据,写入可以并行执行。
容量:给定N个磁盘,RAID-1在镜像级别=2的情况下提供N/2个磁盘的容量。
可靠性:RAID-1允许任何一个磁盘的故障,极端情况下故障可以出现在N/2个磁盘(此时另外N/2个磁盘保留完整数据)。
性能:从单个读取请求的延迟角度来看,它与单个磁盘上的延迟相同。单个写入请求RAID-1需要完成两次物理写入,两次写入可以并行执行,因此依赖于两个写入请求中寻道时间和旋转延迟时间之和最差的请求,因此平均而言比单个磁盘略高。
接下来再从稳态吞吐量方面来看,在执行顺序写入请求时最多只能有N/2个请求同时展开,因此RAID-1获得的最大带宽是(N/2S)MB/s,即峰值的一半。顺序读取的过程比较有趣,我们来看一个例子。假如此时要读取的序列是0、1、2、3、4、5、6、7.假设0映射到磁盘0,1映射到磁盘2,2映射到磁盘1,3映射到磁盘3.此时继续向磁盘0、2、1、3发出读取请求4、5、6、7,问题在于中间会跳过某一块(对于磁盘0来说跳过逻辑块2),当它跳过块时会产生旋转延迟,此时不会为客户提供有用带宽。因此每个磁盘只能提供大约一半的峰值带宽,即(N/2S)MB/s。
随机读可以充分利用每一个磁盘,因此对于随机读取RAID-1的稳态吞吐量为(N*R)MB/s。对于随机写,每次写入都必须执行两次物理写入,因此在所有磁盘都被使用的情况下,客户只能使用带宽的一半。(这里也在一定程度上反映了单次请求和稳态吞吐量的区别)
最后,对于RAID-1我们思考一个问题,一个逻辑写入请求需要执行两次物理写入,如果两次物理写入过程中出现故障(电源掉电,系统故障之类),此时只有一个请求完成,另一个还未完成,当系统恢复后数据就不一致了。而我们期待的是两个磁盘的状态都是一致的(原子性更新的),这也是许多应用面临的被称为一致性更新的问题。我们应该怎么样解决呢?
一种一般性的解决方法是采用某种预写日志(write-ahead logging),首先将要执行的操作记录在日志中,如果发生崩溃,那么在系统恢复后可以根据日志内容进行恢复。但是对于RAID来说每次都在磁盘上记录日志,代价太高。因此很多RAID硬件都提供非易失性的RAM来记录日志,从而获得更高的执行速度,同时也保证了可靠性。
相信聪明的读者会发现一个问题,写入日志过程发生崩溃怎么办,换句话说怎么保证写入日志这个操作的原子性?有兴趣的小伙伴,可以了解一下LFS(日志文件系统)。
RAID4级
RAID-1添加冗余的方法太过昂贵了,我们试图寻找另外一种添加冗余的方法。RAID-4就提供了这样的方法。RAID-4是使用基于奇偶校验位的方法来使用较少的容量保证可靠性,但这样做的代价是性能的损耗。在RAID-4中
添加了一个奇偶校验块用于存储冗余信息,如下:
为了计算就行,我们需要一个数学函数,来确保每一个条带中的数据的可靠性。通常采用异或是一个很不错的办法,给定一组比特,如果比特中1的个数为偶数,XOR会返回0,反之返回1.如下
对于RAID-4我们规定:任何一行的1的个数必须是偶数,以便进行奇偶校验。
对于上面的例子,如果某一列中的数据丢失了,我们可以根据其它列的数据很容易恢复。例如,C2列中的值丢失,我们读取其它列的数据并进行异或获得结果为1,这就是C2中丢失的值。
上面的例子是按照bit级别进行的拆分,同样可以扩展到扇区和块,只需要在对数据块中的每一位上按位XOR,然后再将结果按位放在R中即可。如下展示2个bit为的情况:
可以看出,每个块的每个比特位计算奇偶校验,结果放在P的对应位上。
容量:RAID-4只用一个盘保存冗余位,因此有用容量为(N-1)。
可靠性:RAID-4允许一个磁盘故障,不溶于更多。如果多个磁盘同时故障,无法重建丢失的数据。
性能:首先分析稳态吞吐量,连续读取的可以使用除了奇偶校验盘外所有盘,因此有效带宽是(N-1)*S MB/s。要理解顺序写的性能,我们首先要明白RAID-4是如何进行写入的,RAID-4进行了一种简单的优化称为全条带写入。具体来说,加入0 1 2 3作为请求的一部分发送到RAID,RAID可以简单计算P0的新值,然后将所有的5个块一起并行的写入磁盘。所以RAID-4顺序写入的性能为(N-1)*R MB/s。
接下来分析随机读取的性能,除就校验块外所有的块可以并行读取,因此有效带宽可带到(N - 1)*R MB/s。
对于随机写入,是RAID-4中最复杂的情况,我们来分析为什么是这样。首先我们假设新的写入要覆盖块1,此时奇偶校验位将不再正确反映冗余信息,因此P0也必须更新,那么我们如何有效更新它?
有两种方法,第一种称为加法奇偶校验,工作流程为:并性读取条带中其它所有数据块,并与新块进行异或,结果就是新的校验块,然后再将数据块和校验块写会各自的磁盘。这个方法的关键在于,时间复杂度随着磁盘数量的变化而变化,需要大量的读取操作。
第二种方法是称为减法奇偶校验的方法,想象一下原值为0 0 1 1 原校验位为0,此时用新值覆盖第一个1,此时我们需要读取原值1和原奇偶校验位0.然后比较新数据和就数据如果新数据与原值1相同,保持就校验位不变,否则反转奇偶校验位即可。该流程可用异或实现 Pnew = (Cold XOR Cnew) XOR Pold,同理对块中的每个位执行这个操作即可。
我们使用减法奇偶校验法来对随机写入的性能进行分析。对于每次写入。RAID-4必须执行4次物理IO(两次读取和两次写入)。对于上面的表格我们思考现在提交了2个小的请求,写入块4和块13,数据位于磁盘0和1上,但是就校验位都位于磁盘4上。到这里你应该已经明白了:问题在于随机写入的情况下奇偶校验盘是瓶颈。即是数据盘可以并行但是奇偶校验盘不可以并行,因此所有的随机写操作都被序列化执行。而奇偶校验盘在一次随机写要执行两次IO,因此RAID-4的随机小写入的性能大致为(R/2) MB/s。而且即是你增加磁盘也不会改变这个吞吐量,因此十分糟糕,这也被称为RAID-4的小写入问题。
最后我们来分析RAID-4的单个IO请求的延迟,单次读取只映射到某一个盘,因此同单个磁盘请求延迟相同。单次写入需要两次读取,两次写入,读取和写入可以分别并行执行,因此大约延迟大约是单个磁盘的两倍。(读取时候需要定位,这个时间由最差的来决定,写入时不需要重新定位。)
RAID5
为了解决RAID-4的小写入问题而提出RAID-5,工作原理与RAID-4基本相同,只是将就校验位跨驱动器旋转。如下:
RAID-4与RAID-5的容量和容错能力是相同的。顺序读写性能也相同。单个请求的延迟也相同。
随机读取的性能比RAID-4好一些,因为可以利用所有的磁盘。最后是随机写入,假设此时写入块1和块10,这将变成对磁盘1和磁盘4的请求以及对磁盘0和磁盘2的请求,因此他们可以并行执行。因此如果有大量的随机请求的情况下,我们能保持所有磁盘忙碌,此时随机读取的总带宽是 (N / 4)*R MB/s。4倍的损失是由于RAID-5写入仍然产生4个IO操作,这是使用基于奇偶校验的RAID的成本。
由于RIAD-5与RAID-4基本相同,且在小写入上比RAID-4拥有更好的性能,因此RAID-5基本代取代替了RAID-4,只有一种情况下可以继续使用RAID-4:系统能够保证自己不会执行除了大写入之外的任何事情!
至此,RAID的简单介绍已经结束了,你可以根据RAID这几个级别的性能对比来选择合适的RAID级别。比如,如果你不关心可靠性那么条带化级别很适合你,如果你想要随机IO的性能和可靠性那么镜像是最好的,但是付出的代价是容量下降。如果容量和可靠性是你需求的,那么RAID-5是个不错的选择,付出的代价是小写入的性能。如果你总是执行顺序IO并且希望容量最大化,那么RAID-5也是很不错的。
总结:本文简单介绍了磁盘的结构、与CPU的交互方式(轮询 中断 DMA)、磁盘性能优化的方向(缓存 预读等)、磁盘可能存在的故障和一些解决方法以及RAID的介绍。读者有兴趣可以继续阅读关于磁盘调度算法、文件系统管理等文章,了解更多关于存储的内容。