您的位置:首页 > 其它

flashcache2.0分析(3.0的算法已经改进)

2014-12-26 10:49 267 查看

1. flashcache简介

1.

1.1. 概念

flashcache使用SSD为磁盘提供缓存,大幅度提高随机读写速度。基于linux device mapper框架,具有良好的操作性和可扩展性。本文主要针对2.0版本,3.0算法改进并且支持多块硬盘共享一块ssd,有兴趣的可以阅读下代码。

1.2. 功能

flashcache具有三种模式writeback、writethrough和writearoud。

Ø writeback模式下,后端磁盘的写io先缓存到SSD中,适当的时机再写入后端存储中。读io首先从SSD中读取,如果已经缓存了数据,则直接读取返回,反之则从后端磁盘读取并将数据缓存到SSD中

Ø writethrough模式下,写io将同时写入到SSD和后端磁盘中,读io从SSD中读取。

Ø writearoud模式下,SSD被旁路,所有io操作都针对后端磁盘。

flashcache可以指定缓存或者不缓存特定的pid进程产生的io,flashcache具有white table和black table,某个进程的io数据是否缓存通过以下规则判断:

1. pid位于white table,io数据缓存

2. pid没有设置,tgid位于white table,io数据缓存;

3. pid位于black table,io数据不缓存;

4. pid没有设置,tgid位于black table,io数据不缓存。

flashcache支持顺序io过滤,超过指定大小的连续io将不会被缓存到SSD中,可以提高SSD的利用率,提高随机读写的效率。

1.2.1. flashcache优劣势

模块化设计,不需要编译内核;基于dm框架,良好的扩展性与易用性;不损坏磁盘数据,对后端文件系统透明。

使用接口相对简单,可定制策略较少。

1.2.2. 适用场景

1.3. 系统结构

flashcache由用户态接口和内核模块组成,其结构如下图所示。



内核模块为dm的target驱动,完成SSD缓存的核心功能。用户态接口分为procfs、sysctl和ioctl接口,procfs提供一些flashcache设备的状态信息,sysctl接口提供用户设置flashcache的一些可调参数,ioctl接口实现设备的ioctl,主要提供黑白表的设置功能。

2. flashcache使用

2.

2.1. 环境搭建

2.1.1. 系统依赖

linux系统下内核版本2.6.18和3.2版本之间的内核都支持,大于3.2的内核没有测试过,官方给出的稳定版本为2.6.32版本。

2.1.2. 编译

如果之前没有编译过内核源代码,则下载相应内核的源代码依次运行

make menuconfig

make

make modules_install

make install

如果在/boot下没有生成initrd开头的文件,则需要调用以下命令

mkinitramfs -o /boot/initrd.img-xxx(内核版本号) /lib/modules/xxx(内核版本号)

下载flashcache源代码,进入源代码目录

make KERNEL_TREE=/xxx(编译好的内核源代码目录)

make install

编译之后会生成一个flashcache.ko文件,并拷贝到系统/lib/modules对应目录,此时通过insmod或者modprobe加载flashcache即可。

2.2. 用户态工具

官方提供了4个接口,新增一个接口。

表3-1 flashcache接口
名称
功能
备注
flashcache_create
创建一个flashcache设备
flashcache_load
从一个ssd设备上加载原有的flashcache设备
flashcache_destroy
载一个ssd设备上擦出其上的flashcache设备的元数据
flashcache_remove
快速移除flashcache设备
flashcache_setioctl
flashcache设备的ioctl接口,主要用于设置黑白表
2.3. 接口说明

2.3.1. flashcache_create

名称
flashcache_create
功能
创建一个flashcache设备
命令形式
flashcache_create [-v] [-p back|thru|around] [-b block size] [-m md block size] [-s cache size] [-a associativity] cachedev ssd_devname disk_devname
参数解析
[-p]:缓存模式,back代表writeback模式,thru代表writethrough模式,around代表writearound模式

[-b]:SSD的block大小,以sector为单位,4k的块设置为8,默认4k

[-m]:元素据block大小,以sector为单位,默认4k

[-s]:用来cache的SSD的大小,以sector为单位,默认整个SSD大小

[-a]:set的大小,以SSD的block为单位,默认512个block

cachedev:创建的flashcache设备的名称

ssd_devname:SSD设备的名称,需要/dev目录下的全路径

disk_name:后端磁盘设备的名称,需要/dev目录下的全路径
样例解析
flashcache_create -p back -s 1g -b 4k cachedev /dev/sdc /dev/sdb

创建一个名称为cachedev的flashcache设备,SSD设备/dev/sdc的1G大小用于磁盘设备/dev/sdb作缓存,SSD的block设置为4k,元素据block大小也为4k,整个flashcache设备采用writeback模式
2.3.2. flashcache_load

名称
flashcache_load
功能
从一个ssd设备上加载原有的flashcache设备
命令形式
flashcache_load ssd_devname [cachedev]
参数解析
ssd_devname:需要用来加载的SSD设备,上面存储了flashcache设备的元数据

[cachedev]:重新加载之后的flashcache设备名称,也可以用原来的设备名称

样例解析
例子:flashcache_load /dev/sdc

解释:从/dev/sdc上加载flashcache设备
2.3.3. flashcache_destroy

名称
flashcache_destroy
功能
载一个ssd设备上擦出其上的flashcache设备的元数据
命令形式
flashcache_destroy ssd_devname
参数解析
ssd_devname:存储flashcache设备元数据的SSD设备

样例解析
例子:flashcache_destroy /dev/sdc

解释:删除/dev/sdc上flashcache设备的sb
2.3.4. flashcache_remove

名称
flashcache_remove
功能
快速移除flashcache设备
命令形式
flashcache_remove [-f] cachedev ssd_devname disk_devname
参数解析
[-f]:force选项,代表强制执行

cachedev:flashcache设备的名称

ssd_devname:SSD设备的名称,不需要全路径,只需设备名

disk_devname:后端磁盘的名称,不需要全路径,只需要设备名

样例解析
例子:flashcache_remove -f cachedev sdc sdb

解释:快速移除flashcache设备cachedev
2.3.5. flashcache_setioctl

名称
flashcache_setioctl
功能
flashcache设备的ioctl接口,主要用于设置黑白表
命令形式
flashcache_setioctl (-c | -a | -r) (-b pid |-w pid) ssd_devname
参数解析
[-c|-a|-r]:-c表示清空表中所有pid(tgid),-a表示增加一个pid(tgid)到表中,-r表示从表中删除某个pid(tgid)

[-b|-w]:-b表示黑表,-w表示白表

pid:进程的pid或者tgid

ssd_devname:SSD设备名称,需要全路径

样例解析
例子:flashcache_setioctl -a -w 20368 /dev/sdc

解释:设置进程20368的io可以被缓存
2.4. proc接口

/proc/flashcache目录下的信息为整个模块的信息,/proc/sys/dev/flashcache/<cachedev>目录下为各个flashcache设备的信息。

2.4.1. 基本信息

cache_all: 设置是否启用缓冲,1代表启用,0代表不启用。和setioctl结合起来使用,设置为0时不代表所有io都不会缓存,位于white表中进程的io会缓存。

zero_stats: 清零状态。

reclaim_policy: SSD回收策略。0代表fifo,1代表LRU,可以在运行时动态调整。

io_latency_hist: 计算io延迟,0代表不启用,1代表启用。默认为不启用,因为计算io延迟会引入性能损耗,可以通过dmsetup
status查看io延迟的详情,以250us为单位。

max_pids: 黑白表中最多能设置的pid(tgid)个数。

do_pid_expiry: 控制黑白表中pid有效的时间,0表示不启用控制,1表示启用控制。

pid_expiry_secs: 设置黑白表中pid有效的时长。

skip_seq_thresh_kb: 如果顺序io大于该设置值,则io不会被缓存,该值以kb为单位。如果设置为0,表示缓存所有io,大于0的数值将作为判断顺序io的标准。

2.4.2. write back模式

fallow_delay:设置清理空闲SSD block的间隔时间,默认为900秒,如果设置为0将会禁止清理空闲blocks。

fallow_clean_speed:清理空闲的SSD block时,用来限制每个set内清理的速度,每秒钟每个set清理的block数目不能超过该值,默认为2个。

fast_remove:设置快速移除标志。设置该标志,在移除设备时不会同步SSD中的脏数据到磁盘上,在flashcache设备被重新加载之后,所有脏数据和干净的块都保留在SSD中。

dirty_thresh_pct:每个set内保持的脏块个数,小于该数值脏块的set在未超时情况下不会被清理,清理的过程中,一旦某个set的脏块数目达到该设定值之下则会停止清理该set,其他的脏块通过其他条件判定时候需要清理。

stop_sync:停止SSD向磁盘的同步。

do_sync:开始同步SSD中所有的脏块,此时不再根据回收策略判断。

max_clean_ios_set:每个set可以清理的块数。

max_clean_ios_total:所有set能够清理的块数的和。

3. flashcache技术实现

本章主要从代码级别进行分析,概要性的描述参见第4章。

3.

3.1. 结构图

整个系统的结构图见1.3章节,这里主要介绍flashcache设备内核部分。



flashcache管理SSD和后端存储以及元数据的关系如图所示,SSD的头部会有一个flashcache设备的sb,记录一些设备的全局数据,比如设备的大小,元数据区域的大小等信息。在执行flashcache_load指令的时候,首先读取sb信息,然后从中获知设备的一些整体信息,再加载元数据,从而恢复设备之前的状态。元数据区域的大小是根据SSD大小计算出来的,SSD每一个block对应一项元数据,元数据的大小直接和SSD block的大小相关联,因此选取合适的SSD block大小也是有好处的。内存元数据每一项会保存一个SSD
block和一个后端存储block的映射关系,内存元数据和SSD block是线性映射的关系,和后端存储则是通过其dbn进行hash,某个dbn会被hash到某个set,元数据标记了SSD block当前的状态,是否缓存数据等。

4. 机制详解

本章节结合函数进行概要设计层的分析,涉及到的函数参考第3章。

4.

4.1. 设备创建

flashcache设备使用的是device mapper,创建flashcache设备和创建dm设备是一样的,device mapper的设备层次结构如下图所示。



target device在内核中对应dm_target实例,创建flashcache设备就是实例化target device和mapping table,这几个概念在内核中的关系如下图所示:



每一个dm_target都有个target_type标明是什么类型的设备,比如linear设备就会有个linear_type来标明设备类型,同时target_type也是target device设备的驱动程序,其实现了ctr、dtr、map以及ioctl等接口,这几个接口的含义在第三章已经介绍过。所有对target device的访问最后会调用target_type提供的方法。flashcache设备作为一个内核模块,在其模块加载和初始化过程中向device mapper中注册了其驱动程序。



在创建flashcache设备时,由device mapper层进行一些初始化后,就会调用以上注册的flashcache_ctr进行flashcache设备的初始化。flashcache_ctr的工作见第三章相关函数分析。flashcache设备创建之后,对flashcache设备的访问就会调用该驱动的相关接口,io操作会调用flashcache_map函数,其具体功能见下节,这个函数是flashcache设备的核心。

4.2. 数据管理

flashcache对于SSD、内存和后端存储的管理可以用下图描述其静态特性。



4.2.1. SSD管理

当一个SSD和后端存储组建成flashcache设备时,SSD的空间将会分成3部分:supper block、元数据区域和数据区域。supper block主要保存一些设备的整体参数,例如,SSD大小、SSD block大小、set大小、flashcache设备状态(是否clean shutdown)、flashcache设备名称、后端存储设备名称、后端存储设备大小、flashcache版本以及元数据block大小。这些信息将有助于服务器重启之后重建flashcache设备。

flashcache supper block位于flashcache设备关联的SSD设备的第一个block,当然如果为一块SSD磁盘,前面的2048个sector不包括在内。如果SSD被分成多个分区或是lvm逻辑卷而组成多个flashcache设备,那么每个flashcache设备关联的SSD分区或者逻辑卷的头部都会保存一个sb。

元数据区域紧接着sb,元数据block和SSD block大小不同,可以分别设置,在flashcache设备创建的过程中可以在接口参数中设置。每一个SSD block将会有一个元数据项,flashcache通过SSD的大小和SSD block大小计算block总数,从而可以计算出元数据的大小,flashcache设备在创建过程中会在flashcache_ctr中预留出元数据区域大小。剩下的部分将会作为flashcache的数据缓冲区。所以SSD block大小的选取直接决定了SSD中可以用作数据缓冲区的大小。

4.2.2. 内存元数据

内存中也会保存一份元数据,与SSD上的元数据相比,内存元数据维护了更多的信息,比如每一项内存元数据需要维护操作对应SSD block的任务队列,以及更多的SSD block状态。内存元数据也是每一项对应一个SSD block。无论是SSD上的元数据还是内存元数据,有本节前面的关系图中可以看出,其和SSD block是一个线性映射的关系,所以维护内存元数据采用数组进行线性访问即可。

为了便于管理,提高效率,flashcache将多个SSD block组成一个set,一个set能够容纳的SSD block数为128-4096,通常默认值为512。SSD block按照set的大小被分成了若干个set,每个set都有一个编号,编号线性排列。后端存储通常远远大于SSD的数据缓冲区,那么就会存在多个后端存储的block映射到一个SSD block的情形,flashcache采用hash算法解决这一冲突。flashcache并非简单的将后端存储的dbn和SSD block之间进行hash,flashcache采用了一个更加灵活的算法。后端存储的dbn和set之间进行hash,hash算法并非将某个dbn
hash到了某个SSD block,而是某个set,那么多个连续的后端存储block将会hash到同一个set,其后若干个block之后又会有一段连续的block hash到前面同一个set之中。如本节前面的图中所示,浅绿色和金黄色的线标明了这一情况。因为存在多段连续的后端存储block hash到同一个set的情况,后端存储和SSD set的大小是一个倍数关系,如果在set内还采用线性hash算法,将会大大增加冲突的概率,set内空闲SSD block的利用率也会大大降低。flashcache采用fifo和lru算法管理一个set之内的block,hash到同一个set的后端存储的block都可以通过这两种算法从set内获取一个空闲的SSD
block缓冲数据。

4.2.3. 数据一致性

flashcache中数据一致性包括了很多方面,多个io操作之间的一致性,数据与元数据之间的一致性,uncached io和缓冲io之间的一致性。

由上节可知每一项元数据对应了一个SSD block,每个SSD block拥有多个状态:

/* States of a cache block */

#define INVALID 0x0001

#define VALID 0x0002 /* Valid */

#define DISKREADINPROG 0x0004 /* Read from disk in progress */

#define DISKWRITEINPROG 0x0008 /* Write to disk in progress */

#define CACHEREADINPROG 0x0010 /* Read from cache in progress */

#define CACHEWRITEINPROG 0x0020 /* Write to cache in progress */

#define DIRTY 0x0040 /* Dirty, needs writeback to disk */

/*

* Old and Dirty blocks are cleaned with a Clock like algorithm. The leading hand

* marks DIRTY_FALLOW_1. 900 seconds (default) later, the trailing hand comes along and

* marks DIRTY_FALLOW_2 if DIRTY_FALLOW_1 is already set. If the block was used in the

* interim, (DIRTY_FALLOW_1|DIRTY_FALLOW_2) is cleared. Any block that has both

* DIRTY_FALLOW_1 and DIRTY_FALLOW_2 marked is considered old and is eligible

* for cleaning.

*/

#define DIRTY_FALLOW_1 0x0080

#define DIRTY_FALLOW_2 0x0100

#define FALLOW_DOCLEAN (DIRTY_FALLOW_1 | DIRTY_FALLOW_2)

#define BLOCK_IO_INPROG (DISKREADINPROG | DISKWRITEINPROG | CACHEREADINPROG | CACHEWRITEINPROG)

INVALID和VALID代表了可用性,用以标明一个SSD block是否已经缓存了数据。DISKREADINPROG、DISKWRITEINPROG、CACHEREADINPROG和CACHEWRITEINPROG代表了SSD block对应的io操作性质,当有多个io操作映射到同一个block时,flashcache通过这几个状态来串行化这些io操作,原则上flashcache还维护了一个原子锁,但是其粒度太大,也不适合用在io上下文中,当有一个io操作进行时,其关联的SSD block就会标注以上几种状态中的某一种,这些状态是原子的,处于该状态的block将会挂起后来的io请求,直至当前io解除其状态,通常状态的解除都在io操作的函数中,即是io已经返回了结果。每一个SSD
block的元数据上维护了一个pending 队列,其上排列了被阻塞的对当前block的一些任务,这些任务采用fifo顺序挂到链表中,在合适的实际将会被调度执行。

当某个dbn的io由于某种原因采用了uncached io,而在此过程中,又发生了同一个dbn或者重叠区的缓存io,那么将会发生一致性问题,这个问题在下节中进行阐述。

4.3. flashcache的io

flashcache整个模块的功能主要集中在flashcache设备的io map函数上,flashcache_map实现了io在SSD和后端存储之间的关联与管理,整个flashcache_map的流程图如下所示:



分别针对read和write简要介绍起流程。

对于读io,flashcache首先会检查SSD中是否有block缓存了当前io地址的数据,如果找到意味着一次缓存命中,该任务会直接从缓存读取数据或者加入到等待队列直到读取数据返回,如果在其前面有更新该block的数据,SSD block的数据会是新的,该任务读取的数据也将是新的,因为任务之间是串行执行,所以不会发生一致性问题。如果到目前为止,SSD中没有缓存数据,那么就只能从后端存储直接读取数据了,为了提高以后io效率,将会在SSD block中寻找一空闲的block将读取的数据缓存起来,寻找block的算法参见前面章节。如果在当前set之内找到了空闲的block,同时该io被判定为可缓存。那么将会调用dm_io从后端存储中读取数据,然后将数据写入到找到的SSD
block之中,在此过程中,因为已经找到可用的SSD block,那么就会有对应的元数据项,元数据项的状态会被置为readdisk,而其他操作相同block的io将会被阻塞,这很关键,和后面没有找到SSD block是直接从后端存储读取数据有所不同。如果在SSD中没有找到可用的SSD block,也会直接从后端存储中读取数据,但此时没有元数据项与之对应,就无法阻塞操作相同dbn的其他io操作,也许此时有一个写操作找到了空闲的block并完成了io操作,那么此时uncached io读取的数据就会与cache中的不一致,flashcache处理该问题时,对当前读io不返回结果,其io请求将会再次被提交。在此种情况之下说明给set之内已无空闲block可用,需要清理释放部分资源。

对于写io操作,同样也会查找是否有缓存命中,如果命中将会直接操作缓存,如果没有但是找到了可用的SSD block,同时在建立该block和当前io的dbn之间关系前没有其他任务建立同意dbn和其他block之间的关联关系,则建立它们之间的关联,其后可以和缓存命中一样对待。缓存命中情况下,首先调用dm_io将数据写入SSD block之中,同时顺便清理该set内的空闲block。当io完成之后,通过回调函数检查其io状态,失败则返回错误,成功则需要更新元数据状态,同时写入到SSD中。因为一个元数据block包含了多个数据block的元数据信息,flashcache为每个元数据block维护一个任务链表,加入到该链表的任务都是由每个SSD
block写产生的,它们分别更新元数据block中与之相关联的元数据项。通过链表可以将多个任务合并成一个元数据block的写操作,因为更新元数据block中某一项需要一个整个元数据block的写操作,更新多个元数据项合并之后也只需一次整个元数据block的写操作,提高了效率。更新元数据失败,将会向与之关联的SSD block的io任务返回io错误。这里是向引发本次元数据更新的写io操作返回错误而不是元数据的写io操作,所有写SSD block的io操作对应的元数据都会被标记为diskwriteinprog直到元数据写入到SSD中为止,io操作的结果返回也是元数据写入到SSD中之后返回的。也即是元数据的任务队列增加了更新元数据的效率,同时也增加了io操作的延时。

如果set之内的SSD block满足了被清理的条件(空闲时间超过设定值,默认15min,或者set之内脏块比例达到了设定值)时,将会调用kcopyd内核线程将脏数据从SSD中拷贝到后端存储中,同时会更新元数据,整个过程满足前面所说的一致性。

4.4. flashcahce的wrok

flashcache中无论写SSD还是disk,写数据还是元数据都采用了任务和回调函数组合的方式。每一个io相关操作都封装成一个任务。flashcache在模块初始化函数flashcache_init中创建了用于处理这些任务的工作。

INIT_WORK(&_kcached_wq, do_work);

void

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)

do_work(void *unused)

#else

do_work(struct work_struct *unused)

#endif

{

process_jobs(&_md_complete_jobs, flashcache_md_write_done);

process_jobs(&_pending_jobs, flashcache_do_pending);

process_jobs(&_md_io_jobs, flashcache_md_write_kickoff);

process_jobs(&_io_jobs, flashcache_do_io);

process_jobs(&_uncached_io_complete_jobs, flashcache_uncached_io_complete);

}

通过调用schedule_work就会处理工作_kcached_wq,即是调用do_work函数。通过do_work函数可以看出flashcache初始化了5种任务,分别通过链表_md_complete_jobs、_pending_jobs、_pending_jobs、_io_jobs、_uncached_io_complete_jobs保存各种任务,schedule_work的时候会依次遍历这些链表,处理其上的任务。各个链表上面的任务都按照fifo的顺序排列其上。处理各个链表上任务的函数及其含义见下表。
任务链表
处理函数
任务描述
_md_complete_jobs
flashcache_md_write_done
元数据写入SSD操作之后会创建该任务
_pending_jobs
flashcache_do_pending
有任务正在进行,用来暂存任务
_md_io_jobs
flashcache_md_write_kickoff
写SSD后会创建该任务,用于更新对应的元数据
_io_jobs
flashcache_do_io
针对读未命中时,从后端存储读取数据后会创建该任务,用于将读取的数据写入SSD中
_uncached_io_complete_jobs
flashcache_uncached_io_complete
Uncached io之后会创建该任务,用于检查刚进行的uncached io是否会有一致性问题(针对缓存io)
调用schedule的时机基本上就是一次io操作完成之后,如需进行后续处理时,就会调用schedule_work处理这些任务链表。比如一次读未命中的时候,从后端存储中读取数据之后会创建一个readfill的任务用于将数据写入SSD中,这时候就会调用schedule_work处理任务链表。

void

flashcache_io_callback(unsigned long error, void *context)

{

...

switch (job->action) {

case READDISK:

...

if (likely(error == 0)) {

/* Kick off the write to the cache */

job->action = READFILL;

push_io(job);

schedule_work(&_kcached_wq);

return;

} else {

...

}

break;

...

case WRITECACHE:

...

if (unlikely(error || cacheblk->nr_queued > 0)) {

spin_unlock_irqrestore(&dmc->cache_spin_lock, flags);

push_pending(job);

schedule_work(&_kcached_wq);

} else {

...

}

}

一次元数据写操作之后,也会创建一个任务进行后续的处理,例如刷写脏数据。这个时候也会调用schedule_work处理任务链表。

void

flashcache_md_write_callback(unsigned long error, void *context)

{

struct kcached_job *job = (struct kcached_job *)context;

if (unlikely(error))

job->error = -EIO;

else

job->error = 0;

push_md_complete(job);

schedule_work(&_kcached_wq);

}

一次uncached io完成之后,需要检查本次io的一致性的时候,会创建对应的任务,这个时候也会调用schedule_work处理任务链表。

static void

flashcache_uncached_io_callback(unsigned long error, void *context)

{

...

push_uncached_io_complete(job);

schedule_work(&_kcached_wq);

}

创建任务的时机如下图所示:



flashcache还创建了一个延迟工作delayed_clean,该工作会在某个延时之后对SSD进行全盘空闲块回收。

INIT_DELAYED_WORK(&dmc->delayed_clean, flashcache_clean_all_sets);

这些任务工作有7中状态,其状态对应的元数据状态以及任务是否回应发元数据操作见下表所示。

SSD的元数据和job的状态对应
Job状态
SSD元数据状态
是否需要更新元数据
READCACHE
CACHEREADINPROG

WRITECACHE
CACHEWRITEINPROG

READDISK
DISKREADINPROG

WRITEDISK

READFILL
DISKREADINPROG

INVALIDATE

WRITEDISK_SYNC

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: