基于blkio-cgroup的虚拟机IO隔离测试

1.    简介

blkio是基于cgroup实现的一个IO控制器,支持两种IO控制策略:

  • 基于时间权重的带宽分配(Proportional Weight division of bandwidth):按照设定的权重,为不同的group分配不同比例的带宽;
  • 读写速度控制(Throttling/Upper Limit policy):可以设置精确的读写速率(bps或者iops)上限值
  • xen0内核配置:

2.    测试

2.1 测试环境搭建

tlinux-dom0的内核,没有打开cgroup选项,需要打开CONFIG_CGROUPS

 

基于权重的带宽分配,还需要打开以下选项:

CONFIG_BLK_CGROUP=y

CONFIG_CFQ_GROUP_IOSCHED=y

 

IO限速,还需打开以下选项:

CONFIG_BLK_CGROUP=y

CONFIG_BLK_DEV_THROTTLING=y

  • 虚拟机配置:

两台tlinux 1.2虚拟机以HVM方式运行,VCPU数量均为4,内存大小4096M,分别拥有两块虚拟磁盘,其中第一块磁盘(hda)15G,第二块磁盘(hdb)10G。

  • 测试工具:

使用FIO工具(http://freecode.com/projects/fio)分别测试虚拟机在顺序/随机读写,同步/异步,DIRECT/非DIRECT模式下的带宽占用情况

FIO配置文件示例(随机读,同步,DIRECT模式):

[global]

description=Emulation of Intel IOmeter File Server Access Pattern

 

[iometer]

bssplit=4k/30:8k/40:16k/30          #随机读4k文件占30%、8k占40%、16k占30%

rw=randread                      #读写方式为随机读

direct=1                         #使用无缓存机制,这样测试硬盘io更准确

size=1g                          #文件大小为1G

ioengine=sync                    #io引擎使用sync方式

directory=/data/testfile              #测试文件

numjobs=32                      #并发进程数

group_reporting                   #汇总每个进程的信息

  • 配置带宽分配

l  dom0挂载cgroups和blkio

mount -t tmpfs cgroup_root /sys/fs/cgroup

mkdir /sys/fs/cgroup/blkio

mount -t cgroup -o blkio none /sys/fs/cgroup/blkio

l  为两台虚拟机各创建一个group

mkdir -p /sys/fs/cgroup/blkio/test1/ /sys/fs/cgroup/blkio/test2

找到两台虚拟机hdb盘对应的blkback内核线程的id,假设分别为id1,id2

/bin/echo id1 > /sys/fs/cgroup/blkio/test1/tasks

/bin/echo id2 > /sys/fs/cgroup/blkio/test2/tasks

l  分别为两个group设置权重

echo 1000 > /sys/fs/cgroup/blkio/test1/blkio.weight

echo 500 > /sys/fs/cgroup/blkio/test2/blkio.weight

l  两台虚拟机同时启动fio,进行测试

  • 配置IO限速

l  dom0挂载cgroups和blkio

mount -t tmpfs cgroup_root /sys/fs/cgroup

mkdir /sys/fs/cgroup/blkio

mount -t cgroup -o blkio none /sys/fs/cgroup/blkio

l  为VM1 hdb盘配置IO限速

找到vm1 hdb盘对应的device mapper设备的主设备和次设备号,如下图所示:

 

将读、写带宽均设置为10M/s:

echo "252:5  10485760" > /sys/fs/cgroup/blkio/blkio.throttle.read_bps_device

echo "252:5 10485760" > /sys/fs/cgroup/blkio/blkio.throttle.write_bps_device

l  VM1启动fio,进行测试;VM1测试完毕后,VM2(未限速)启动fio测试

2.2 测试结果

2.2.1 FIO sync,direct测试

带宽分配测试结果

 

VM1(权重1000)

VM2(权重500)

比例

IOPS

BW(KB/s)

IOPS

BW(KB/s)

随机读

487

4465.2

243

2238.3

2.00

随机写

797

7266.3

464

4237.6

1.71

随机读写

读:181

写:181

读:1663.7

写:1672.9

读:84

写:84

读:776.3

写:779.1

2.15

顺序读

4649

42762

930

8554.9

4.99

顺序写

6225

57275

3164

29116

1.97

顺序读写

读:3643

写:3649

读:33562

写:33585

读:1856

写:1860

读:17083

写:17115

1.96

 

 

 

 

 

 

 

 

 

 

 

 

 

 

IO限速测试结果

 

 

VM1(限制读10M/s,写10M/s)

VM2(未限速)

IOPS

BW(KB/s)

IOPS

BW(KB/s)

随机读

974

8891.7

1142

10380

随机写

1083

9855.8

1255

11384

随机读写

读:370

写:373

读:3370.7

写:3416.4

读:388

写:389

读:3544.2

写:3554.2

顺序读

1115

10241

8960

82407

顺序写

1113

10239

9323

85794

顺序读写

读:1054

写:1056

读:9695.5

写:9729.6

读:5556

写:5562

读:51093

写:51171

 

 

 

 

 

 

 

 

 

 

 

 

2.2.2 FIO libaio,direct测试

带宽分配测试结果

 

 

VM1(权重1000)

VM2(权重500)

比例

IOPS

BW(KB/s)

IOPS

BW(KB/s)

随机读

492

4507.1

242

2217.1

2.03

随机写

877

7991.3

436

4009.3

2.01

随机读写

读:292

写:300

读:2683.9

写:2740.6

读:139

写:140

读:1288.2

写:1284.3

2.10

顺序读

4288

39462

945

8698.5

4.54

顺序写

19196

176650

9496

87370

2.02

顺序读写

读:6786

写:6794

读:62455

写:62521

读:1373

写:1370

读:12614

写:12631

4.94

 

 

 

 

 

 

 

 

 

 

 

 

IO限速测试结果:

 

VM1(限制读10M/s,写10M/s)

VM2(未限速)

IOPS

BW(KB/s)

IOPS

BW(KB/s)

随机读

1045

9510.3

1157

10515

随机写

1119

10167

1416

12833

随机读写

读:634

写:689

读:5747.6

写:5808.7

读:625

写:628

读:5675.2

写:5705.7

顺序读

1114

10241

7887

72524

顺序写

1112

10239

45946

422706

顺序读写

读:1096

写:1094

读:10079

写:10074

读:20133

写:20124

读:185134

写:185160

 

 

 

 

 

 

 

 

 

 

 

 

 

2.2.3 FIO sync,indirect测试

带宽分配测试结果

 

 

VM1(权重1000)

VM2(权重500)

比例

IOPS

BW(KB/s)

IOPS

BW(KB/s)

随机读

677

6185.3

284

2608.4

2.38

随机写

146183

988714

119368

811517

1.22

随机读写

读:3559

写:3560

读:30290

写:30311

读:283

写:281

读:2589.2

写:2569.4

11.87

顺序读

461300

4144.4MB/s

450084

4043.7MB/s

1.02

顺序写

144173

1295.2MB/s

137143

1232.5MB/s

1.05

顺序读写

读:106687

写:106663

读:981586

写:981244

读:86988

写:86994

读:800211

写:800399

1.23

 

 

 

 

 

 

 

 

 

 

 

 

IO限速测试结果:

 

 

VM1(限制读10M/s,写10M/s)

VM2(未限速)

IOPS

BW(KB/s)

IOPS

BW(KB/s)

随机读

247542

1622.6MB/s

251816

1647.8MB/s

随机写

19648

134629

249705

1635.4MB/s

随机读写

读:109537

写:109578

读:736113

写:736351

读:126227

写:126188

读:847167

写:846925

顺序读

441337

3964.9MB/s

900815

8093.4MB/s

顺序写

53945

496427

261125

2346.2MB/s

顺序读写

读:132120

写:132118

读:890538

写:890548

读:200216

写:200219

读:1798.8MB/s

写:1798.8MB/s

 

 

 

 

 

 

 

 

 

 

 

 

2.2.4 FIO libaio,indirect测试

带宽分配测试结果

 

 

VM1(权重1000)

VM2(权重500)

比例

IOPS

BW(KB/s)

IOPS

BW(KB/s)

随机读

664

6075.1

278

2554.6

2.39

随机写

147173

993947

135270

913545

1.09

随机读写

读:8587

写:8582

读:64935

写:64948

读:270

写:269

读:2470.9

写:2457.4

31.80

顺序读

404176

3631.3MB/s

401973

3611.7MB/s

1.01

顺序写

132955

1194.8MB/s

132849

1193.5MB/s

1.00

顺序读写

读:90984

写:91013

读:837038

写:837502

读:89743

写:89724

读:825527

写:825397

1.01

 

 

 

 

 

 

 

 

 

 

 

 

IO限速测试结果:

 

VM1(限制读10M/s,写10M/s)

VM2(未限速)

IOPS

BW(KB/s)

IOPS

BW(KB/s)

随机读

234995

1549.2MB/s

232928

1535.2MB/s

随机写

249820

1638.9MB/s

269994

1774.8MB/s

随机读写

读:200422

写:200469

读:1314.5MB/s

写:1314.9MB/s

读:141837

写:141889

读:952479

写:952738

顺序读

385586

3464.7MB/s

793800

7131.7MB/s

顺序写

53472

491891

243404

2186.6MB/s

顺序读写

读:45131

写:45082

读:415201

写:414802

读:203259

写:203250

读:1826.2MB/s

写:1825.1MB/s

 

 

 

 

 

 

 

 

 

 

 

 

 

2.3测试结论

FIO测试配置

预期结果

测试结果

分析说明

sync,direct

1. 带宽分配测试,VM1和VM2的带宽比为2:1

2. 限速测试,VM1读、写带宽均不超过10M/s

1. 除了“顺序读”,其它读写模式下VM1,VM2的带宽比例,均达到预设值1000:500,即2:1

2. 各种读写模式下,带宽均未超过预设数值

“顺序读”测试结果为5:1,原因待调查。

libaio,direct

带宽分配和限速均不起作用

1.带宽分配测试,除“顺序读”,“顺序读写”,VM1、VM2的带宽比例均为2:1

2. 限速测试,带宽均未超过预设数值

blkio-cgroup文档指出只能控制同步(sync)读写请求,测试结果表明它对异步(libaio)读写也有效,还要进一步调查(本文尾部已更新)

sync,indirect

带宽分配和限速均不起作用

符合预期

在非direct模式下,读写要经过page cache,无法获取真实的磁盘IO性能

libaio,indirect

2.    代码分析

2.1     带宽分配

代码在block/cfq-iosched.c中,需要先理解一下CFQ,后面补上。

3.2 IO限速

blk-throttle的主要代码在内核源码树的block/blk-throttle.c文件中。基本思路是:hook IO路径上的generic_make_request()函数,如果不需要限速或者未超出速度上限,则由generic­_make_request()继续处理,否则throttle模块接管该bio,把他暂存起来,延迟一段时间后交给工作队列分发出去。

3.2.1 数据结构

每个限速的group都有一个struct throtl_grp结构,所有的throtl_grp根据其group的“层级”,组织成一个树状结构(红黑树),这个结构中几个重要的字段是:

       struct throtl_grp {

       ……

       struct rb_node rb_node; /* 所有的group组织成一颗红黑树,用rb_node串起来 */

       unsigned long disptime; /* 何时处理因限速而没有分发的bio? */

       struct bio_list bio_lists[2]; /* 暂存队列,存放因限速而没有递交给下层处理的bio,

bio_lists[0]是读队列,bio_lists[1]是写队列,下同 */

       uint64_t bps[2];  /* 读、写带宽限制,单位是字节/秒,-1表示没有限制 */

       unsigned int iops[2];  /* 读、写IOPS限制,-1表示没有限制 */

 

/* 限速是以时间片为单位的,每个时间片的长度是100ms(即HZ/100,存放在全局变量throtl_slice中。时间片的起始时间是slice_start,结束时间是slice_end,bytes_disp是当前时间片内已经分发的字节数,io_disp存放当前时间片内已经分外的IO个数 */

       uint64_t bytes_disp[2];

       unsigned int io_disp[2];

       unsigned long slice_start[2];

unsigned long slice_end[2];

       ……

}

另外,为了在处理每个IO请求时能定位到其所在的group,在每个请求队列(struct request_queue)添加了一个throtl_data结构指针:

       struct throtl_data {

       ……

              struct throtl_grp *root_tg;  /* 根group */

              struct delayed_work throtl_work; /* 用来分发bio的工作队列 */

              ……

       }

3.2.2 代码流程

IO限速的核心函数是blk_throtl_bio(),它在__generic_make_request()中被调用。__generic_make_request()根据blk_throtl_bio()的返回值来决定如何处理一个bio,

  • 如果blk_throtl_bio()返回0,表明无需限速或者当前读写速度未达到设定的上限,__generic_make_request()继续处理
  • 如果blk_throtl_bio()返回非0,则说明该bio因为限速而不能立即分发,throttle模块会来处理这个bio,__generic_make_request()直接跳到end_bio

 

blk_throtl_bio()的逻辑是:找到对应的cgroup,如果该group没有限速或者,则返回0;否则,把bio挂到throtl_grp结构的bio_list里暂存起来,等待一个合适的时间,由工作队列把这些bio分发给下层:

  • 如果bio设置了REQ_THROTTLED标志,说明这是一个之前因限速而阻塞的bio,现在又被递交过来,这次不能再阻塞了,清除REQ_THROTTLED标志并返回0;
  • 调用task_blkio_cgroup()获取当前进程所在的blkio_cgroup,放在blkcg变量中
  • 调用throtl_find_tg()找到对应的throtl_grp结构,放到tg变量中
  • 如果当前的group没有设置限速(tg_no_rule_group()返回非0),调用blkiocg_update_dispatch_stats()更新一些统计值,返回0
  • 调用tg_may_dispatch(),这个函数会根据用户设置的限速值判断是否能分发此bio,如果不能,会返回此bio需要等待的时间。流程是:

n  如果当前throtl_group的bps和iops均为-1,无需限速;

n  限速时间片长度为throtl_slice(定义为HZ/100,即100ms),如果上一个时间片已经用完,则分配一个新的时间片;否则,调整时间片的长度,使得从当前时间开始,到时间片结束,中间的时间间隔是throtl_slice。

n  调用tg_with_in_bps_limit()和tg_with_in_iops_limit()判断当前bio是否超出限速设置,以tg_with_in_iops_limit()为例:

u  jiffy_elapsed_rnd初始化为当前时间和时间片起始时间的间隔,为了方便计算,把jiffy_elapsed_rnd向上取整为throtl_slice的倍数;

u  在不超出速度上限的情况下,当前时间片内允许发送的字节数为 bps[rw] * jiffy_elapsed_rnd / HZ

u  如果当前时间片内已发送字节数加上本次bio的字节数小于上述允许值,则可以分发此bio;否则,计算该bio需要等待多久才能分发,返回等待时间;

n  返回等待时间;

  • 如果该bio不能立即分发,调用throtl_add_bio_tg()把它加入throtl_group的等待队列bio_lists里;
  • 调用throtl_schedule_next_dispatch(),通知工作队列去处理未分发的bio;

 

延迟分发bio的工作是由工作队列来处理的,blk_throtl_init()里,调用INIT_DELAYED_WORK将工作队列的处理函数初始化为blk_throtl_work(),这个函数找到对应的请求队列,然后调用throtl_dispatch(),将throtl_group暂存队列中的bio取出,并调用generic_make_request()分发出去。

3.    blkio限制

1.  blkio目前只支持同步IO(sync IO)的带宽控制。因为blkio基于cgroup,cgroup是一个进程组的概念,只有确定IO请求属于哪个进程(组),才能对其进行资源控制。在sync IO时,可以通过当前任务(current)来定位属主进程,async IO时,缺少跟踪和定位属主进程的手段(可能根本定位不到,比如page cache回写磁盘时,进程已经退出)。社区有些解决异步IO带宽控制的patch(http://lwn.net/Articles/429292/http://lwn.net/Articles/430069/),但是没有被接收。

2.  带宽分配策略依赖于cfq,工作在IO调度器层,并非所有的设备都能使用。比如多数device mapper设备(如facebook家的flashcache),一些快速的ssd设备。这些设备的driver为了避免block层的冗长路径,一般直接将bio转换DMA。另外,cfq是基于时间片的,虽然有iops模式,但是如果一旦挂上cgroup,在各个cgroup都比较饱满工作的条件下,IO时延较大。

3.  IO限速在通用块层实现,适用于所有IO请求。但是IO带宽控制过于硬性,无法弹性变化。即使其他cgroup都很空闲,IO速度也不能超过设定的上限,造成一定浪费。

===========================================================

2012-10-11更新:

 

上次测试时,对异步IO的测试结果还留有疑问,blkio的文档(Documentation/cgroups/blkio-controller.txt)里提到:

Currently only sync IO queues are support. All the buffered writes are

still system wide and not per group. Hence we will not see service

differentiation between buffered writes between groups.

第一句说,“只支持同步IO队列”,言外之意是异步IO不支持吗?

 

 

什么是异步IO(AIO)?下面的图很清楚的表明,AIO要同时满足两个条件:异步和非阻塞。

 

 

 

在Linux系统上,异步IO(AIO)有两种实现方式:

POSIX AIO在用户态使用多线程同步来模拟的异步IO,其特点有:

(1)       支持所有的文件系统

(2)       通用于所有移植了glibc的操作系统

(3)       对缓冲读写(即没有设置O_DIRECT标志)也有效

POSIX AIO性能较差,因为可以并行的IO操作受限于线程数量。

在内核态实现的异步IO(Linux 2.6.22+),性能较好,但在使用时有很多限制:

(1)       打开文件时必须指定O_DIRECT,如果不指定,AIO操作不会失败,但是实际的运行结果可能不是异步的,或者会在操作过程中阻塞,违反了AIO非阻塞的语义。(http://lse.sourceforge.net/io/aio.html指出有支持buffered filesystem AIO的patch)

(2)       读写的的大小和偏移要对齐到文件系统的块大小(block size)

 

从以上两种AIO的实现方式来看,他们更多的关注了AIO“异步”的语义,对于“非阻塞”的语义,并未过度纠缠。

 

AIO对blkio-controller有何影响?

从实现方式可以看出,对于Kernel AIO,blkio-controller仍然起作用;但对于POSIX AIO,blkio-controller会失效。因为POSIX AIO会生成其他线程执行IO操作,而不是你在cgroup里限制的那个进程。这就是解释了为什么blkio-controller里指出“only sync IO queues are support”。

以上是物理机的情形,而在虚拟机上,虚拟机内部所有的磁盘操作都由dom0的blkback内核线程来中介,所以只要限制了blkback线程,不管虚拟机内部是同步IO还是异步IO,blk-iocontroller都能发挥作用。这解释了我们的FIO libaio的测试结果

posted on 2013-01-22 17:54  jackiedai  阅读(1397)  评论(0编辑  收藏  举报

导航