您的位置:首页 > 其它

深入浅出Flashcache(一)

2011-12-05 22:47 218 查看
转载自:http://www.ningoo.net/html/2011/all_things_about_flashcache_1.html

在计算机系统中,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

[2]. Block devices and volume management in Linux
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: