深入浅出Flashcache(一)

转载自:http://www.ningoo.net/html/2011/all_things_about_flashcache_1.html 

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

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

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

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

· write backup: 先写入到cahce,然后cache中的脏块会由后台定期刷到持久存储。

· write through: 同步写入到cache和持久存储。

· write around: 只写入到持久存储。

在详细的介绍Flashcache之前,需要先了解一下Linuxblock devicedevice mapper相关的知识。

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

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

块设备在用户空间是一种特殊的文件类型,由(major,minor)来标识,major区分diskminor区分partitionLinux中一般把设备文件放在/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-rr– 1 root root 8, 1 2011-12-03 11:54 /opt/sda1

由于块设备处于文件系统和物理设备驱动之间,在这一层做一些工作可以对所有IO产生影响,因此很多优秀的产品都在这一层做文章,除了Flashcache,还有一个比较著名的就是DRBDDRBD已经进入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_devicebd_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_headfsblock deviceio请求单元,现在已经改为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调度排序后进入块设备层,会尝试合并到已有的requstbio结构中的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请求队列,队列中包含多个requestrequest_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也是一个很有用的工具,下面是2intel L5630的主机上打印出来的信息,L5630intel的低功耗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
[2]. Block devices and volume management in Linux


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值