(本文里说的“资源隔离”主要是指cgroup根据blkio.weight的值来按比例调配io的带宽和IOPS,不包括io-throttle即blkio.throttle.xxx的一系列配置,因为linux的io-throttle机制不依赖于IO调度器)
由于现在的SSD越来越快也越来越便宜,如何高效的利用SSD就变得十分重要。高效利用SSD有两个办法,一个是加大应用程序发出的io深度(比如用aio),对于无法加大io深度的一些重要应用(比如mysql数据库),则可以用第二个办法:在一个高速块设备上跑多个应用实例。
现在问题来了,既然跑多个实例,就要避免它们互相干扰,比如一个mysql由于压力大把io带宽都占了,这对另一个mysql实例就不太公平。现在常用的办法当然是用cgroup来做io资源的隔离(参考这篇),但是,有个尴尬的事情,目前只有cfq这一个io调度器是支持cgroup的,而cfq调度器在高速设备上表现却不尽人意。
deadline调度器准备了两个队列,一个用来处理写请求,一个用来处理读请求,然后根据io快要到期的时间(即deadline)来选择哪个io该发出去了,这个选择同时也要考虑“读比写优先”、“尽可能连续发射io”等约束,但是不管怎么说,既然把io的响应时间做为了该调度算法的第一要素,那deadline调度器的io延时性就是相对最好的(即response time很短),它也是SSD设备的推荐调度器。问题在于,“两个队列”,这结构太简单了,自然也无法支持cgroup这样复杂的特性,所以,虽然deadline调度器很快,RT很短,但是做不了资源隔离。
再来看cfq,cfq最早就是基于磁盘来做优化的,它的算法尽可能保证io的连续性,而不保证io的及时响应(所以,在对磁盘做高io压测的时候,cfq调度器有时会制造出长达几秒的io响应来),比如,对于单个io,它会做一个小小的等待,看有没有和这个io连上的下一个io到来,如果有,可以一起发出去,以形成连续的io流,但是,这个”小小的等待“,就大大延长了io响应时间。cfq调度器为cgroup中的每个group单独创建一个”struct cfq_queue“实例,然后以各group的weight所占的权重来决定改发哪个group的io,所以cfq是支持资源隔离的。我讲的这些还没有碰到cfq的皮毛,它四千多行的代码有很多复杂和技巧性的算法和数据结构,我估计只有fusionio的李少华能把它说清楚,我这里就不班门弄斧了。
介绍IO调度器的文章,可参考这篇。
总的来说,deadline适合SSD设备,但是它不支持资源隔离;cfq支持资源隔离,但是在SSD上跑延时又太差,且代码复杂,不易改造。(noop铁定不支持资源隔离,而anticipator跟deadline代码相差不大,这两个都出局了)最终,经过我、高阳、伯瑜三个人的讨论,得出决定:如果要方便应用方的使用,最好是做一个新的、代码非常简单的新IO调度器。这样,应用方如果是用硬盘,那就选择cfq;如果是用SSD,就选择deadline;如果既用了高速设备,又想做资源隔离,那就选这个新调度器。既方便应用方灵活选择,又方便我们自己维护。(如果把新调度器做进deadline,后期的维护和backport就会越来越吃力)。新调度器的原理很简单:通过类似cfq的xxx_queue结构来记录各group的信息,针对每个group都创建一个list,存放从该group到来的io,然后在dispatch io时,用该设备的io request容量(即nr_request)减去已经发出但还没有处理完成的io数(即rq_in_driver),得出的就是该设备还可处理的io数(即下面代码中的quota),然后根据这个“可处理io数”和各group的权重,算出各group的list上可以dispatch的io数,最后,就是按照这些数去list上取io,把它们发出去了。
static int tpps_dispatch_requests(struct request_queue *q, int force) { struct tpps_data *tppd = q->elevator->elevator_data; struct tpps_group *tppg, *group_n; struct tpps_queue *tppq; struct list_head *next; int count = 0, total = 0, ret; int quota, grp_quota; if (unlikely(force)) return tpps_forced_dispatch(tppd); if (!tppd->total_weight) return 0; <span style="color: red;">quota = q->nr_requests - tppd->rq_in_driver;</span> if (quota < MIN_DISPATCH_RQ) return 0; list_for_each_entry_safe(tppg, group_n, &tppd->group_list, tppd_node) { if (!tppg->nr_tppq) continue; tpps_update_group_weight(tppg); grp_quota = (quota * tppg->weight / tppd->total_weight) - tppg->rq_in_driver; ......
这样,就是按cgroup的权重比例来发出io了,而且,不像cfq一个队列一个队列的处理,这个新调度器是拿到quota后从每个group list里都取一定量的io,所以也还保证了一定的“公平性”,同时,quota是按照设备的处理能力算出来的,所以也能保证尽可能把设备打满。简单、按比例并发处理(准确的说,是公平处理),所以我们给这个新io调度器起名字叫“Tiny Parallel Proportion Scheduler”,简称tpps。代码已经合并到了ali_kernel,patch链接是1,2,3,4,5,6
另外申明一下,这个调度器并不复杂,也不高端,讲求的是简单好用,所以如果有哪位内核高手看到这个调度器后发表不满:“这么简单也好意思拿出来说?!”,那大可不必。我们就是做出来解决问题的,问题解决就ok,不必做得那么高深。我写这篇文章也只是方便有兴趣的DBA或者SA来试用和提建议,不是要说明这个调度器有多牛逼。
想要使用tpps调度器需要使用ali_kernel:
git clone git@github.com:alibaba/ali_kernel.git git checkout dev #编译并重启内核
机器重启后
modprobe tpps-iosched #再用 cat /sys/block/sdX/queue/scheduler 可以看到多个io调度器,其中有tpps echo tpps > /sys/block/sdX/queue/scheduler
我用一个快速设备测试了一下cfq和tpps对cgroup的支持,fio测试脚本如下:
[global] direct=1 ioengine=libaio runtime=20 bs=4k rw=randwrite iodepth=1024 filename=/dev/sdX numjobs=4 [test1] cgroup=test1 cgroup_weight=1000 [test2] cgroup=test2 cgroup_weight=800 [test3] cgroup=test3 cgroup_weight=600 [test4] cgroup=test4 cgroup_weight=400
测试结果:
各个cgroup的IOPS
io调度器 | test1 | test2 | test3 | test4 |
---|---|---|---|---|
cfq | 6311 | 5334 | 3983 | 2569 |
tpps | 8592 | 8141 | 7152 | 5767 |
各个cgroup的平均RT(Response Time)单位:毫秒
io调度器 | test1 | test2 | test3 | test4 |
---|---|---|---|---|
cfq | 161 | 221 | 241 | 398 |
tpps | 114 | 127 | 141 | 177 |
各个cgroup的最大RT(Response Time)单位:毫秒
io调度器 | test1 | test2 | test3 | test4 |
---|---|---|---|---|
cfq | 312 | 387 | 445 | 741 |
tpps | 173 | 262 | 260 | 322 |
从测试看来,tpps既能实现资源隔离,同样的设备和io压力下性能也略高于cfq。
重要提示:对tpps来说,只有当设备被压满时才会有不同cgroup的不同iops比例出现,所以,测试时压力一定要大。
转载请注明转自: 斯巴达第二季 , 本文固定链接: tpps: 一个可做cgroup资源隔离的高效IO调度器
转载请注明:爱开源 » cgroup资源隔离的高效IO调度器