技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 算法 --> 深入浅出Flashcache(一)

深入浅出Flashcache(一)

浏览:2428次  出处信息

    Cache is king.

    在计算机系统中,cache的魔爪无处不在。CPU中有L1,L2,甚至L3 cache;Linux有pagecache,MySQL有buffer cache/query cache;IO系统中Raid卡/磁盘也有cache;在大型互联网系统中,数据库前面一般也都有一层memcache。Cache是容量与性能之前取平衡的结果,以更低的成本,获得更高的收益,是系统设计时应该遵循的原则。

    传统机械硬盘几十年来,容量不断翻倍的增长,相比较而言,性能的增长就慢的像蜗牛了。对于依赖IO性能的应用,典型的如数据库,一直在等待新的技术来拯救。在此之前,身躯庞大的高端存储,动辄重达几吨。相比于存储里带的硬盘来说,价格贵得离谱,而存储的附加价值,在于io在大量硬盘之间的均衡分布,以及IO链路的多路容灾,以及部分固件层面的优化和数据保护等。

    Flash disk(SSD/FusionIO等)的出现,改变了这一切。Flash disk将硬盘从机械产品变成了电气产品,功耗更小,性能更好,时延更优,看起来传统硬盘已经不堪一击,数据库欢欣鼓舞,新的革命似乎将一夕成功。但新东西也有它致命的缺陷,价格和经过时间检验的稳定性。

    所以Facebook的Mohan Srinivasan在2010年开源了Flashcache,将Flash disk做为普通硬盘的cache,这个思路,目前一些尝试也在raid卡硬件层面做尝试,例如LSI的CacheCade Pro,不过之前版本新浪的童鞋测试过似乎性能没有想象的好。Flashcache在淘宝一些核心数据库中已经在线运行了大半年,经过调优后的表现稳定。Flashcache利用了Linux的device mapping机制,将Flash disk和普通硬盘的块设备做了一层映射,在OS中变现为一块普通的磁盘,使用简单,是一个值得推荐的方案。Flashcache最初的实现是write backup机制cache,后来又加入了write through和write around机制:

  • write backup: 先写入到cahce,然后cache中的脏块会由后台定期刷到持久存储。
  • write through: 同步写入到cache和持久存储。
  • write around: 只写入到持久存储。
  •     在详细的介绍Flashcache之前,需要先了解一下Linux的block device和device mapper相关的知识。

        1. Block Device

         块设备最初主要是依据传统硬盘等IO操作较慢的设备而设计的,所以Linux中为块设备的IO操作提供了cache层,所以基于块设备的请求一般是buffer io,当然后来由于数据库等自己有cache机制的应用,os/fs层面的cache就成了多余,所以出现了绕过os/fs层cache的direct io。

        块设备在设备确定层和kernel之间,为Kernel提供了统一的IO操作接口,同时隐藏了不同硬件设备的细节。当有多个并发IO请求到块设备时,请求的顺序会影响IO的性能,因为普通的机械硬盘需要移动机械臂,所以kernel一般会对IO做排序等调度后再发送到块设备层。IO调度算法是一种电梯算法(elevator algorithm),目前主要有cfq/deadline/anticipatory/noop,其中cfq是Linux的默认策略;anticipatory在新的内核中已经放弃;deadline在大部分OLTP数据库应用中更具优势,IO的响应时间更稳定些;noop只对IO请求排序,其他不干涉,在FusionIO等IO性能很好的设备上,noop反而更具优势,所以FusionIO的驱动默认使用了noop。关于IO Scheduler,后文会有更详细的解释。

        块设备在用户空间是一种特殊的文件类型,由(major,minor)来标识,major区分disk,minor区分partition。Linux中一般把设备文件放在/dev目录。实际上你完全可以将块设备文件创建到其他地方,只要(major,minor)唯一确定,块设备文件最后访问的起始同一个块设备。

        $ls -l /dev/sda1

         brw-rw—- 1 root disk 8, 1 2011-12-03 01:00 /dev/sda1

        $sudo mknod /opt/sda1 b 8 1

        $ls -l /opt/sda1

         brw-r-r- 1 root root 8, 1 2011-12-03 11:54 /opt/sda1

        由于块设备处于文件系统和物理设备驱动之间,在这一层做一些工作可以对所有IO产生影响,因此很多优秀的产品都在这一层做文章,除了Flashcache,还有一个比较著名的就是DRBD(DRBD已经进入2.6.33内核)。

        

        在Linux内核中定义了一些操作块设备相关的结构体和函数,下面的信息基于2.6.32.49:

        1.1 gendisk

        gendisk保存了一个具体的disk的信息,包括该disk上的请求队列,分区列表/第一个分区,块设备操作表等重要信息。

    struct gendisk {
        struct request_queue *queue;
        struct disk_part_tbl *part_tbl;
        struct hd_struct part0;
        const struct block_device_operations *fops;
        ...
    };
    

        1.2 hd_struct

        hd_struct保存一个分区信息,包括起始扇区,扇区数,分区号等基本信息。

    struct hd_struct {
        sector_t start_sect;
        sector_t nr_sects;
        int      partno;
        ...
    };
    

        1.3 disk_part_tbl

        disk_part_tbl保存磁盘分区表的信息

    struct disk_part_tbl {
        struct rcu_head rcu_head;
        int len;
        struct hd_struct *last_lookup;
        struct hd_struct *part[];
    };
    

        1.4 block_device

        block_device可以是整个磁盘,也可以是一个分区。如果是一个分区块设备,则bd_contains会指向分区所在磁盘的block_device,bd_part则指向分区信息结构hd_struct。。

    struct block_device {
        dev_t                    bd_bdev;
        struct inode            *bd_inode;
        struct list_head         bd_inodes;
        struct super_block      *bd_super;
        struct block_device     *bd_contains;
        struct gendisk          *bd_disk;
        struct hd_struct        *bd_part;
        struct list_head         bd_list;
        struct backing_dev_info *bd_inode_backing_dev_info;
        ...
    };
    

        1.5 buffer_head

        顾名思义,在内核层对块设备的IO请求是以块为单位的。buffer_head是一个块在内存中的元数据信息。b_data指向该块数据的实际地址。b_this_page则将通过一page中的块连接起来。以前版本的buffer_head是fs到block device的io请求单元,现在已经改为bio了。

    struct buffer_head {
        unsigned long        b_state;
        struct buffer_head   *b_this_page;
        char                 *b_data;
        sector_t              blocknr;
        struct block_device  *b_bdev;
        bh_end_io_t          *b_end_io;
        ...
    };
    

        1.6 bio

        bio封装了一次实际的块设备io请求。这是块设备io请求的基本单位。bi_vcnt表示bio_vec的数目。

    struct bio {
        sector_t             bi_sector;
        struct bio          *bi_next;
        struct block_device *bi_bdev;
        unsigned short       bi_vcnt;
        unsigned short       bi_idx;
        struct bio_vec      *bi_io_vec;
        ...
    };
    

        1.7 bio_vec

        bio_vec表示一次bio涉及到的数据片段(segment),由所在内存页地址,长度,偏移地址等定位。一次bio一般包含多个segment。

    struct bio_vec {
        struct page            *bv_page;
        unsigned int            bv_len;
        unsigned int            bv_offset;
    };
    

        1.8 request

        块设备层IO等待请求(pending I/O request)。内核中的bio请求在经过io调度排序后进入块设备层,会尝试合并到已有的requst。bio结构中的bi_next将队列中的bio请求串成一个队列。bio/biotail域指向队列的首尾。

    struct request {
        struct list_head            queuelist;
        struct bio                 *bio;
        struct bio                 *biotail;
        void                       *elevator_private;
        void                       *elevator_private2;
        struct gendisk             *rq_disk;
        request_queue_t            *q;
        ...
    };
    

        1.8 request_queue

        request_queue维护块设备层IO请求队列,队列中包含多个request。request_queue同时定义了处理队列的函数接口,不同的设备注册时需要实现这些IO处理接口。

    struct request_queue {
        struct list_head            queue_head;
        struct request             *lastmerge;
        elevator_t                 *elevator;
        struct request_list         rq;
        request_fn_proc            *request_fn;
        make_request_fn            *make_request_fn;
        prep_rq_fn                 *prep_rq_fn;
        unplug_fn                  *unplug_fn;
        merge_bvec_fn           *merge_bvec_fn;
        prepare_flush_fn        *prepare_flush_fn;
        softirq_done_fn         *softirq_done_fn;
        rq_timed_out_fn         *rq_timed_out_fn;
        dma_drain_needed_fn     *dma_drain_needed;
        lld_busy_fn             *lld_busy_fn;
        struct blk_trace           *blk_trace;
        ...
    };
    

        1.9 submit_bh

        submit_bh是内核发送IO请求给块设备的函数,目前较新版本的内核中该函数会调用submit_bio执行实际请求。

    int submit_bh(int rw, struct buffer_head * bh)
    {
        struct bio *bio;
        int ret = 0;
        ...
        bio = bio_alloc(GFP_NOIO, 1);
    
        bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9);
        bio->bi_bdev = bh->b_bdev;
        bio->bi_io_vec[0].bv_page = bh->b_page;
        bio->bi_io_vec[0].bv_len = bh->b_size;
        bio->bi_io_vec[0].bv_offset = bh_offset(bh);
    
        bio->bi_vcnt = 1;
        bio->bi_idx = 0;
        bio->bi_size = bh->b_size;
    
        bio->bi_end_io = end_bio_bh_io_sync;
        bio->bi_private = bh;
        bio_get(bio);
        submit_bio(rw, bio);
        if (bio_flagged(bio, BIO_EOPNOTSUPP))
            ret = -EOPNOTSUPP;
    
        bio_put(bio);
        return ret;
    }
    

        1.10 submit_bio

        submit_bio函数会调用generic_make_request执行实际的bio请求。generic_make_request则循环处理bio链表,针对每个bio调用内联函数__generic_make_request来做处理。__generic_make_request则最终调用request_queue中的make_request_fn处理函数处理实际的IO请求。

    void submit_bio(int rw, struct bio *bio)
    {
        ...
        generic_make_request(bio);
    }
    
    ...
    static inline void __generic_make_request(struct bio *bio)
    {
        struct request_queue *q;
        int ret;
        ...
        do{
          q = bdev_get_queue(bio->bi_bdev);
          ...
          ret = q->make_request_fn(q, bio);
        }while(ret);
        ...
    
    }
    

        2. Block device相关的工具

        Linux提供了一些工具来操作和查看块设备,如果你的系统中没有,可以安装最新版本的util-linux-ng来获得,实际上很多常用的工具都是出自整个工具集,本文后续也会用到其中一些有意思的工具。

        2.1 lsblk

        RHEL6.1中已经带有该工具。下面是一台已经配置好Flashcache的机器上执行的结果:

    $lsblk
    NAME                MAJ:MIN RM   SIZE RO MOUNTPOINT
    sda                     8:0    0   200G  0
    ├─sda1                8:1    0   128M  0 /boot
    ├─sda2                8:2    0  14.7G  0 /
    ├─sda8                8:8    0     2G  0 [SWAP]
    sdb                     8:16   0   1.5T  0
    └─sdb1                8:17   0   1.5T  0
      └─cachedev (dm-0) 253:0    0   1.5T  0 /opt
    fioa                  252:0    0 300.4G  0
    └─cachedev (dm-0)   253:0    0   1.5T  0 /opt
    

        2.2 blkid

        blkid可以块设备的属性,不带参数也会列出系统中所有的块设备。

    $ sudo blkid
    /dev/sda1: UUID="0ff3ff63-d214-4d32-8633-66a4333fece9" TYPE="ext4"
    /dev/sda6: UUID="d328b838-9043-438d-81b8-6a96454def3c" TYPE="swap"
    

        2.3 blockdev

        

         blockdev,不仅可以查看,也可以设置块设备的一些属性。

    $ blockdev
    
    用法:
      blockdev -V
      blockdev --report [devices]
      blockdev [-v|-q] commands devices
    
    可用的命令:
    	--getsz                        获得512字节的段大小
    	--setro                        设置只读
    	--setrw                        设置读写
    	--getro                        获得只读
    	--getss                        get logical block (sector) size
    	--getpbsz                      get physical block (sector) size
    	--getiomin                     get minimum I/O size
    	--getioopt                     get optimal I/O size
    	--getalignoff                  get alignment offset
    	--getmaxsect                   get max sectors per request
    	--getbsz                       获得块大小
    	--setbsz BLOCKSIZE             设置块大小
    	--getsize                      获得 32-bit 段数量
    	--getsize64                    获得字节大小
    	--setra READAHEAD              设置 readahead
    	--getra                        获取 readahead
    	--setfra FSREADAHEAD           设置文件系统 readahead
    	--getfra                       获取文件系统 readahead
    	--flushbufs                    刷新缓存
    	--rereadpt                     重新读取分区表
    

        2.4 fdisk

        当然常用的fdisk也是管理块设备的利器。

    $ sudo fdisk -l /dev/sda4
    
    Disk /dev/sda4: 136.5 GB, 136492089344 bytes
    255 heads, 63 sectors/track, 16594 cylinders
    Units = cylinders of 16065 * 512 = 8225280 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disk identifier: 0x00000000
    

        2.5 blktrace

        blktrace是跟踪块设备IO请求情况的利器。

        blktrace is a block layer IO tracing mechanism which provides detailed information about request queue operations up to user space.

        核心系统部的褚霸童鞋详细的介绍了这个个工具,有兴趣的移步这里这里,还有这里

        2.6 lscpu

        顺带说一下,lscpu也是一个很有用的工具,下面是2路intel L5630的主机上打印出来的信息,L5630是intel的低功耗CPU,额定功率只有常用的x5620的一半左右。

    $sudo lscpu
    Architecture:          x86_64
    CPU op-mode(s):        32-bit, 64-bit
    Byte Order:            Little Endian
    CPU(s):                16
    On-line CPU(s) list:   0-15
    Thread(s) per core:    2
    Core(s) per socket:    4
    CPU socket(s):         2
    NUMA node(s):          2
    Vendor ID:             GenuineIntel
    CPU family:            6
    Model:                 44
    Stepping:              2
    CPU MHz:               2127.973
    BogoMIPS:              4255.85
    Virtualization:        VT-x
    L1d cache:             32K
    L1i cache:             32K
    L2 cache:              256K
    L3 cache:              12288K
    NUMA node0 CPU(s):     0,2,4,6,8,10,12,14
    NUMA node1 CPU(s):     1,3,5,7,9,11,13,15
    

        未完待续

        参考:

         [1]. Linux Block Device Architecture

    建议继续学习:

    1. Ubuntu工作机使用FlashCache技术加速    (阅读:5343)
    2. 深入浅出Flashcache(三)    (阅读:2023)
    3. 深入浅出Flashcache(五)    (阅读:1906)
    4. 深入浅出Flashcache(二)    (阅读:1871)
    5. 深入浅出Flashcache(四)    (阅读:1869)
    QQ技术交流群:445447336,欢迎加入!
    扫一扫订阅我的微信号:IT技术博客大学习
    © 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

    京ICP备15002552号-1