深入浅出Flashcache(一)
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机制:
在详细的介绍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
建议继续学习:
- Ubuntu工作机使用FlashCache技术加速 (阅读:5343)
- 深入浅出Flashcache(三) (阅读:2023)
- 深入浅出Flashcache(五) (阅读:1906)
- 深入浅出Flashcache(二) (阅读:1871)
- 深入浅出Flashcache(四) (阅读:1869)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:NinGoo 来源: NinGoo.net
- 标签: Flashcache
- 发布时间:2012-02-05 23:27:46
- [69] Twitter/微博客的学习摘要
- [65] 如何拿下简短的域名
- [64] IOS安全–浅谈关于IOS加固的几种方法
- [63] android 开发入门
- [62] Go Reflect 性能
- [62] find命令的一点注意事项
- [60] 流程管理与用户研究
- [58] 图书馆的世界纪录
- [58] 读书笔记-壹百度:百度十年千倍的29条法则
- [58] Oracle MTS模式下 进程地址与会话信