从原理到实现,RAID5原理详解及代码实现浅析

前文我们介绍了Linux下面的RAID技术基本原理,并通过RAID1介绍了其大致的实现流程。今天我们介绍一下RAID5,因为RAID5还是比较复杂的,因此觉得有必要介绍一下。

RAID5的算法

关于RAID5的算法,我们在网上看到最多的一张图恐怕就是下面这张图了。这张图基本上说明了RAID5的算法,但还不够。在这张图中讲清了RAID5的关键是有一个校验数据块,校验数据块按照规则分布在不同的磁盘。

 

 

从原理到实现,RAID5原理详解及代码实现浅析_第1张图片

图1 RAID5算法示意图

验和的计算非常简单,主要使用了位运算中的异或运算。异或运算是逐位进行运算,规则是是相同为0,不同为1,下面是具体的示意图。

 

从原理到实现,RAID5原理详解及代码实现浅析_第2张图片

图2 异或运算示意图

通过上述运算规则就可以解决单块磁盘损坏的情况。为了便于理解,我们举一个简单的例子。比如我们5块磁盘组成的RAID5,其中4块数据盘,一块校验盘。假设里面存储的数据非常简单,分别是0和1,具体如图3所示。假设一块磁盘坏了(红色),那么我们可以通过剩下的磁盘中的数据经过异或运算得到故障磁盘的数据(具体如何运算大家自己思考一下)。

从原理到实现,RAID5原理详解及代码实现浅析_第3张图片

图3 磁盘容错原理

RAID5的校验数据块正是基于这种简单的规则进行计算的,但是差别在于RAID5一次性计算的数据块比较大

 

另外一点,根据校验数据块与实际数据块在磁盘的布局的差异,RAID5有多种算法,最常见的包括:左对称(Left-symmetric)、左不对称(Left-asymmetric)、右对称(Right-symmetric)和右不对称(Right-asymmetric)等等,具体如图4所示。

从原理到实现,RAID5原理详解及代码实现浅析_第4张图片

图4 RAID5算法

上述算法都比较简单,对照图4可以很容易的理解,本文不再赘述。除此之外还有RAID5E和RAID5EE等布局算法,本文暂时不做介绍,后续再详细介绍。

RAID5基本概念

 

前面文章我们已经对Linux下RAID的整体架构和代码做过介绍,因此今天将省略该部分的介绍。本文重点介绍一下RAID5对IO的处理流程。当然,这个要结合前文对RAID5算法的介绍。前面我们知道对于IO请求RAID公共层最终会调用个性接口,图1是RAID1的流程,RAID5的主流程与此一致,并没有太大差别,主要差异在RAID5处理函数(raid5_make_request)内部。

从原理到实现,RAID5原理详解及代码实现浅析_第5张图片

图5 RAID请求处理流程

在介绍具体代码之前我们先解释几个概念,这将对我们理解代码非常有帮助。这几个概念分别是扇区(sector)、实现条带(stripe)和块(chunk)。可以结合图6理解上述概念。

从原理到实现,RAID5原理详解及代码实现浅析_第6张图片

图6 基本概念图示

 

扇区(sector):在块子系统中处理IO的基本单元,大小为512字节。

 

实现条带(stripe):这个是在Linux内核中具体实现的概念,也就是计算单元。在Linux内核中并不是对整个逻辑条带进行计算,然后写数据到磁盘的,而是划分为更小的粒度,也就是实现条带。实现条带在每个磁盘上大小为条带深度,目前是4KB,也就是将RAID成员盘每个4KB进行异或运算(这一点对理解代码很重要)。

 

块(chunk):一个条带(可以称为逻辑条带)在一个具体数据盘(成员盘)上的大小。通常这个大小是可以配置的,可以是8KB、16KB,甚至512KB。

 

逻辑条带:逻辑条带是数据布局的依据,RAID算法根据逻辑条带计算数据在磁盘的具体位置及数据与校验数据的相对位置。在图6中每两行就是一个逻辑条带。

从原理到实现,RAID5原理详解及代码实现浅析_第7张图片

图7 数据处理示意图

我们通过一个具体的例子来说明一下上述概念的关系。如图7所示是将图6中的第一个条带(为了区分,我们后面称为逻辑条带)放大后的效果。图中的数字是数据的位置,单位是4KB,也就是分别是1对应0-4KB,2对应4KB-8KB,依次类推。在上图中,我们假设逻辑条带在一个磁盘上的大小是8KB。实现条带大小是4KB,这个是固定的。

 

通过上面数据我们可以计算出来每个逻辑条带可以存储的数据大小是24KB(8KB*3)。这样如果上层连续写入24KB数据的时候,这些数据在磁盘上的具体位置如图7所示。这里需要注意的是,如果IO大于实现条带的深度(4KB)那么就会被切割为4KB,并且要保证4KB对齐。如果数据没有填满逻辑条带深度,那么数据要先将一个成员盘填满后才会进入下一个成员盘,正如图7所示。

 

通过上述分析,我们基本上清楚了RAID5的基本概念,并且明确了这些概念与实际数据位置的对应关系。下面我们针对Linux内核的代码分析一下其主要的处理流程。

 

RAID5代码简析

 

关于RAID的创建流程我们在前面文章中已经介绍过了,这部分内容大同小异,本文不再介绍。RAID5的代码还是比较复杂的,很难在一篇文章中介绍全面。今天,本文主要介绍RAID5的读写流程。

 

在介绍之前,我们先回忆一下本文中的图5,里面有关于RAID1的读写概要流程。在该流程中会调用RAID的个性函数进行IO处理,而对于RAID5而言,该函数就是raid5_make_request。也就是说函数raid5_make_request是RAID5请求处理的入口。

在介绍细节之前我们先介绍一下流程概况,同时介绍一个关键的结构体。对于RAID5来说,数据处理流程主要分为两大步:

第一步是进行IO预处理,这里主要是根据具体的布局算法和参数计算出请求所处的实现条带,并将其加入实现条带中。最终,将实现条带加入到一个待处理链表中。

第二步是在一个守护线程(raid5d)中完成的。该线程在链表中有数据的时候会被唤醒,并进行具体的处理。这里主要包括进行校验数据的计算和将数据提交到物理磁盘等操作。

从原理到实现,RAID5原理详解及代码实现浅析_第8张图片

图8 RAID5核心流程

 

当然,这里仅仅是简单的描述了一下核心流程。RAID5的处理流程非常多,也非常复杂。处理请求处理流程外,还包括RAID转换(takeover)处理流程、异常修复和参数调整(reshape)处理流程等等。今天我们聚焦请求处理,其它流程后续介绍。

 

除了主要流程外,有一个数据结构非常重要,这里需要重点介绍一下。该数据结构就是stripe_head,这个结构体表示一个实现条带。也就是说,RAID5软件模块是以此为单位进行IO处理的。

从原理到实现,RAID5原理详解及代码实现浅析_第9张图片

图9 stripe_head结构体

该数据结构中包括位置、磁盘数量、状态、校验盘位置和成员盘信息。其中成员盘也就是具体的物理磁盘,这个用一个名为r5dev的数据结构表示。在该数据结构中我们可以看到关联的IO及存储数据的页指针。由于每个成员盘数据结构只有一个页指针,而我们知道内核中一个页的大小通常是4KB。这也是为什么RAID5的实现条带处理请求的大小是4KB的原因。

 

前面铺垫了这么多,接下来我们介绍一下请求处理流程中的一些细节。

 

1IO拆分

由于实现条带处理IO的粒度是4KB,因此如果请求大于4KB的情况下就会被拆分。如图是raid5_make_request函数中的一段代码。其中for循环就是对请求进行拆分的流程。在拆分之前需要计算出该请求的初始扇区和终止扇区。

从原理到实现,RAID5原理详解及代码实现浅析_第10张图片

图10 IO拆分

2初始化条带并入队

 

完成拆分之后,请求的最大粒度就是4KB了。此时会调用函数raid5_compute_sector根据具体的RAID算法该请求逻辑位置转换为物理位置。也就是该请求应该在哪个数据盘、哪个校验盘及物理位置是什么等。

 

从原理到实现,RAID5原理详解及代码实现浅析_第11张图片

图11 raid5_compute_sector函数

前面我们介绍过处理请求的关键数据结构是stripe_head。该数据结构在软件初始化的时候会预分配,系统默认分配256个。这些实现条带通过哈希表的方式存储,当使用的时候通过扇区偏移获取可用的实现条带。获取实现条带的函数就是raid5_get_active_stripe,使用完后会被释放。

 

如果所有实现条带都被用完,那么这个请求就会被阻塞在这里(大并发情况下会出现)。如果仍然有可用的条带,那么上面函数就会返回该实现条带。然后后面流程中就可以通过add_stripe_bio函数将请求添加到该条带中。

 

最后调用release_stripe_plug函数将条带添加到链表中,然后唤醒守护线程进行后续流程的处理。

3IO的处理

 

IO的具体处理过程由守护线程完成,具体函数为raid5d。该函数会循环获取待处理的IO进行处理。为了提升性能和可靠性,其实这里的处理逻辑还是比较复杂的。今天我们进行简化处理,只考虑主要流程,其它流程后续再介绍。

 

核心处理流程在函数handle_active_stripes中,该函数实现了对条带数据的处理。该函数会调用handle_stripe函数进行具体的处理。handle_stripe主要调用两个函数,一个是raid_run_ops,另外一个是ops_run_io。前者实现校验数据块的计算,而后者则是将请求提交到物理磁盘。至此,整个IO的处理完成。

 

但是,实际处理流程比我们描述的要复杂的多。因为有各种细节需要处理,比如需要从物理磁盘读取数据进行计算、非对齐的处理等等。限于篇幅,本文介绍一下主要流程,更多细节我们后续专门介绍。

新的RAID形式

传统RAID已经几十年的历史了。但是随着磁盘容量的越来越大,传统RAID最突出的矛盾有些不可调和。这个矛盾主要是磁盘容量太大的时候出现故障磁盘重构的时间太长了。

 

我们举一个例子,现在细节最大的磁盘是16TB。如果用这么大容量的SATA磁盘做一个RAID5,出现磁盘故障,那么重构时间大概是46.6(16*1024*1024/100)小时,也就是近2天2夜的时间。想想,在这段时间内相当于在裸奔,多么恐怖的事情。

 

因此,一种新的RAID形势出现了。这种RAID并不是对整个磁盘做RAID,而是将磁盘切割为若干个逻辑区域,对逻辑区域做RAID。这样相当于减少了RAID成员磁盘的大小,从而是重构时间大大缩短。

从原理到实现,RAID5原理详解及代码实现浅析_第12张图片

图12 新的RAID形势

 

在互联网领域还有另外一种数据可靠性方法,就是通过多副本的方式。其原理非常简单,就是按照某种随机算法,将数据同时发送到多块(通常是3块)磁盘上。当出现磁盘故障的时候通过数据的重新定位来保证数据副本的数量。

 

关于新的RAID形势并非本文内容,这里只是作为一个展望。后续,我们将详细介绍相关的内容。如果觉得本号的文章还行,能够有所收获,请大家不要吝惜点赞啊!

你可能感兴趣的:(存储,Linux内核,linux)