Raid1源代码分析开篇总述

  前段时间由于一些事情耽搁了,最近将raid1方面的各流程整理了一遍。网上和书上,能找到关于MD下的raid1的文档资料比较少。决定开始写一个系列的关于raid1的博客,之前写过的一篇读流程也会在之后加一些修改,我阅读的代码的linux内核版本是2.6.32.61。进入实验室的时间不长,关于磁盘管理等内核方面的理解不足或者有误之处,希望批评指正。

一、Raid1简介

  Raid1又称为镜像磁盘阵列。由两块或者多块盘构成这样一个阵列,并且每块盘所存储的内容都是完全一致的。意思就是盘与盘之间互为镜像,而在上层看来只有一块盘。比如说,如果现在有两块1T的盘,做成一个raid1阵列,那么在用户看来只有一块1T的盘。

  这样虽然明显降低了磁盘的利用率,但是却提高了数据的安全性。如果其中有一块盘坏了,数据仍然没有丢失,因为数据在其他盘中还有备份。只是盘阵会处于降级状态,那么这种情况,只需要用热备盘来替换损坏盘即可。

  Raid1中的流程可以分为三大部分:读、写、同步。读请求会选择盘阵中的一块盘中进行读操作,写请求会对盘阵中每一块盘进行写操作,同步请求会将最新数据的盘内容读取出来,然后写入其他盘中,达到同步效果。

  下面将从以下几个部分来进行展开介绍:

  1. Raid1相关数据结构
  2. Raid1相关策略
  3. 初始化流程分析
  4. 读流程分析
  5. 写流程分析
  6. 同步流程分析
  7. raid1d函数流程分析
  8. bitmap相关分析(bitmap会在后续单独分析)

 

二、Raid1相关数据结构

1 r1_bio结构

  struct r1bio_s {

    atomic_t              remaining; //未完成的请求数

    atomic_t              behind_remaining;    //延迟写的未完成请求数

    sector_t               sector;  //请求扇区号

    int                       sectors; //请求扇区数

    unsigned long       state;   //状态

    mddev_t              *mddev;     //raid1设备

    struct bio             *master_bio;       //记录上层下发的bio

    int                        read_disk;   //读请求的盘号

    struct list_head     retry_list;    //重试请求链表

    struct bitmap_update       *bitmap_update;  //没啥用

    struct bio             *bios[0];     //指向盘阵中每个盘的bio

  };

  这里的r1_bio主要用来管理raid1中相关的bio。通过最后一个*bios[0]柔性数组来达到管理raid1下发到每个盘的bio,通过*master_bio来管理raid1接收到上层的bio。

  remaining字段和behind_remaining字段,对于延迟写的判断起到了重要作用,在写流程中会有具体分析。

  bitmap_update字段没什么用,在更高的代码版本中,直接将这个字段删除了。

 

2conf结构

  struct r1_private_data_s {

    mddev_t                     *mddev;      //raid1设备

    mirror_info_t             *mirrors;      //盘阵的首地址指针

    int                 raid_disks;    //盘数

    int                 last_used;    //最后一次读操作的盘号

    sector_t        next_seq_sect; //顺序的下一个扇区

    spinlock_t            device_lock; //设备锁

    struct list_head   retry_list;  //重试请求链表

    struct bio_list             pending_bio_list;       //正常的读写请求链表

    struct bio_list             flushing_bio_list;

    spinlock_t            resync_lock;        //同步锁

    int                 nr_pending;  //pending队列请求数

    int                 nr_waiting;   //等待请求数

    int                 nr_queued;      //retry队列的请求数

    int                 barrier;            //是否设置屏障,raid1自己的一套barrier

    sector_t        next_resync;    //下一个要同步的扇区 

    int                 fullsync;          //设置是否全部同步

    wait_queue_head_t   wait_barrier;  //进程队列,通过这个切换回守护进程

    struct pool_info   *poolinfo;

    struct page          *tmppage;    //临时页

    mempool_t *r1bio_pool;          //正常请求的r1bio结构的缓冲池

    mempool_t *r1buf_pool;         //同步请求的r1bio结构的缓冲池

  };

  这里r1_private_data_s是用来管理整个raid1本身的。其中的pending_bio_list和retry_bio_list两个队列是守护进程与其他进程交互的“纽带”,守护进程只需处理这两个队列即可。

  flushing_bio_lsit字段没什么用,在更高的代码版本中,直接将这个字段删除了。

 

三、Raid1相关策略

1WriteMostly

  WriteMostly是对raid1成员盘设置的一种属性,用户决定哪些成员盘是否设置为WriteMostly盘,或者一个盘也不设置WriteMostly。WriteMostly盘认为是“慢盘”。对于设置了WriteMostly的盘,尽量避免从该盘中读数据。在read balance中有体现,是优先读非WriteMostly盘。假如镜像的速度很慢,这样的设置是非常有效的。

  可以只设置WriteMostly盘而不设置Write Behind模式,设置WriteMostly盘是Write Behind的前提。是否启动write behind模式,取决于超级块的设置。

 

2Write Behind

  只有设置了WriteMostly盘,Write Behind才有意义。Write Behind模式意味着,只要数据写入了非WriteMostly模式的磁盘,就认为写入成功返回。如果没有设置write behind模式的话,则需要写入所有盘后才能认为写入成功。

  设置Write Behind模式后,如果所有非WriteMostly盘写入成功,则通知上层“所有磁盘写入成功”,raid1层接收的上层bio即可结束返回。但实际情况并非上层“看到”的这样,因为还有可能存在WriteMostly盘还未完成写操作。所以一定存在Behind pages与WriteMostly盘对应,这是在raid1中保存的接受上层的bio的页结构的一份拷贝数据,在所有盘的写请求都成功返回后,才能把behind pages这份拷贝释放,并释放r1_bio结构。Behind pages用来保证写请求能最终完成,是写请求完整性和正确性的一个必要的保障。

  例如,raid1中有两个成员盘。一个成员盘是本地磁盘,另一个成员盘是局域网中的一块盘,则将局域网的磁盘设置WriteMostly盘(因为比较“慢”),然后开启write behind模式。这样在写入数据时,本地磁盘数据写完后,raid则认为该次写操作已完成。

 

3Read balance

  Read balance是读均衡算法。在读请求的时候,通过read balance来选取一块盘进行读操作。读均衡算法达到了在尽量避免从WriteMostly盘读数据的前提下,对于其他每个成员盘读请求负载尽量一致,并且提高读取速度。具体流程分析在“Raid1源代码分析--读流程(重新整理)”文中有详细说明。

 

4barrier

  在raid1中有两种barrier。一种是接收到上层的bio所具有的barrier属性,一种是raid1自身实现的一套barrier。

  对于接收到上层的具有barrier属性的bio,该barrier属性由用户决定是否设置,只有在写操作的时候会出现。当md收到一个barrier bio请求时,md会先把在该barrier     bio之前到达的bio完成,然后再完成barrier bio,之后再处理在barrier bio之后到达的bio。通过bio中的标记为BIO_RW_BARRIER来判断是否设置了barrier。

  对于raid1自身实现的一套barrier,只有在raid1进行同步操作的时候设置,而且同步时一定会设置。因为在同步过程中是不能让其他读写请求进入raid1层“打扰”同步操作的,所以raid1会自己设计一套barrier,来满足该需求。通过conf->barrier的增减,来控制barrier的设置,并通过判断conf->barrier是否为0,来决定是否阻拦读写操作,而让读写操作进入等待状态。

 

5bitmap

  Bitmap主要是为了保证数据的可靠性。在写的过程中,有可能存在不稳定的因素,比如磁盘损坏,系统故障等,这样导致写入失败,在系统恢复后,raid1也需要进行恢复,传统的恢复方式就是全盘扫描计算校验或者全量同步,如果磁盘比较大,那同步恢复的过程会很长,有可能再发生其他故障,这样就会对业务有比较大的影响。以raid1来说,在发生故障时,其实两块盘的数据很多都是已经一致的了,可能只有少部分不一致,所以就没必要进行全盘扫描,但是系统并不知道两块盘哪些数据是一致的,这就需要在某个地方记录哪些是已同步的,为此,就诞生了bitmap,简单来说,bitmap就是记录raid中哪些数据是一致的,哪些是不一致的,这样在raid进行恢复的时候就不用全量同步,而是增量同步了,从而减少了恢复的时间。

  bitmap的一个bit对应盘阵的一个chunk,在盘阵数据写入前,设置该chunk对应的bit,盘阵写入完成,则清除该bit。要进行同步时,参照bitmap,只有置位的bit对应的chunk才需要进行同步,这样缩短了同步的时间,提高了效率。这是最简单的一种思路。 

  注:bitmap小节的两段,参考资料 http://www.bitstech.net/2014/01/27/linux软raid的bitmap分析/

                                   http://blog.csdn.net/qincp/article/details/4396517

 

6pending_listretry_list

  pending_list为正常请求的队列,retry_list为重试请求队列,这两个队列是守护进程与其他进程交互的枢纽。其他进程将bio或者r1_bio添加队列中就完成了自己的任务,唤醒守护进程;守护进程只操作这两个队列,先将pending_list中的请求一次下发,然后逐一处理retry_list中的请求。只有在两个队列都为空的情况下,守护进程才会退出。pending_list中的每一项为bio,retry_list中的每一项为r1_bio,因为pending_list中的存放的是正常请求,所以直接是bio结构下发即可,而retry_list是重试请求,需要对整个盘阵的bio进行重新处理,所以是r1_bio结构。

 

本文来自fangpei的博客,转载请标明出处:http://www.cnblogs.com/fangpei/

原文地址:https://www.cnblogs.com/fangpei/p/3853004.html