IO解惑:cephfs、libaio与io瓶颈

最近笔者在对kernel cephfs客户端进行fio direct随机大io读测试时发现,在numjobs不变的状况下,使用libaio做为ioengine,不管怎么调节iodepth,测试结果都变化不大,咦,这是为何呢?php

 

1、撇开fio,单看libaio的使用

......
    rc = io_setup(maxevents, &ctx);
    for (j = 0; j < IO_COUNT; j++) { ...... io_prep_pread(iocb, fd, (void *)buf_, io_size, offset); } rc = io_submit(ctx, IO_COUNT, &iocbray[count]); ...... rc = io_getevents(ctx, IO_COUNT, IO_COUNT, events, &timeout); ...... } 

代码中,io_setup函数建立一个异步io的上下文,io_prep_pread函数准备了IO_COUNT个读请求,经过io_submit函数批量提交IO_COUNT个读请求,最后经过io_getevents函数等待请求的返回。
笔者经过该代码来对kernel cephfs客户端进行direct随机读测试,发现io_submit函数很是耗时,这彻底不符合笔者对libaio的预期(io_submit提交请求应该很是快,时间应该耗费在io_getevents等待io结束上)。node

笔者决定一探究竟...缓存

2、探索libaio源码

SYSCALL_DEFINE3(io_submit, aio_context_t, ctx_id, long, nr,struct iocb __user * __user *, iocbpp){ return do_io_submit(ctx_id, nr, iocbpp, 0); } long do_io_submit(aio_context_t ctx_id, long nr,struct iocb __user *__user *iocbpp, bool compat){ ... for (i=0; i<nr; i++) { ret = io_submit_one(ctx, user_iocb, &tmp, compat); } ... } static int io_submit_one(struct kioctx *ctx, struct iocb __user *user_iocb,struct iocb *iocb, bool compat){ ... ret = aio_run_iocb(req, compat); ... } static ssize_t aio_run_iocb(struct kiocb *req, bool compat){ ... case IOCB_CMD_PREADV: rw_op = file->f_op->aio_read; ... } // 后面的代码再也不赘述 

这段是3.10.107内核的io_submit系统调用的源码,并不复杂,总结下就是,对于批量的读请求,io_submit会逐个经过io_submit_one函数进行提交,而io_submit_one最终是调用底层文件系统的aio_read函数进行请求提交。
这里说的底层文件系统固然是cephfs文件系统,不妨来看下它的aio_read函数。异步

3、探索kernel cephfs源码

const struct file_operations ceph_file_fops = { ... .read = do_sync_read, .write = do_sync_write, .aio_read = ceph_aio_read, .aio_write = ceph_aio_write, .mmap = ceph_mmap, ... }; 

经过上述代码能够看出,kernel cephfs的aio_read上注册的是ceph_aio_read函数,让咱们看看该函数。async

static ssize_t ceph_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos){ ... if ((got & (CEPH_CAP_FILE_CACHE|CEPH_CAP_FILE_LAZYIO)) == 0 || (iocb->ki_filp->f_flags & O_DIRECT) || (inode->i_sb->s_flags & MS_SYNCHRONOUS) || (fi->flags & CEPH_F_SYNC)) /* hmm, this isn't really async... */ ret = ceph_sync_read(filp, base, len, ppos, &checkeof); else ret = generic_file_aio_read(iocb, iov, nr_segs, pos); ... } 

相信你已经注意到了这条注释 /* hmm, this isn't really async... */,在direct读模式下,当上层的io_submit调用到这里时,并无进行async的调用,而是sync调用,即请求发送后需等待结果返回。函数

在3.10.107内核下,因为kernel cephfs没有实现真正的aio,致使批量提交的请求,io_submit会逐一处理提交,而后等待请求结果,再处理下一请求,而非批量提交请求,批量等待请求结果,这即是io_submit耗时的缘由。测试

4、回到fio

理解了io_submit为何费时,也就能理解fio下以libaio做为ioengine,不管怎么调节iodepth,测试结果都变化不大的缘由。因此,当底层文件系统不支持aio时,fio测试时,libaio跟sync是几乎没有差异的。this

5、聊聊cephfs、libaio、fio

4.14内核上,kernel cephfs在实现上支持了libaio,笔者分别作了以sync和libaio为ioengine的fio direct随机大io读测试。spa

对于sync:
在numjobs数达到必定的值后,fio的带宽已经到达了瓶颈(远小于客户机的万兆网卡带宽、集群有36个sata osd),再提升numjobs数已经再也不起做用,这一点笔者很是费解,缘由不得而知,知晓缘由的朋友能够评论中告知笔者,万分感谢。code

对于libaio:
在numjobs设置成较小值(四、8)时,经过增大iodepth就能够打满kernel cephfs客户机的万兆网卡(测试文件较小,集群osd足以将其缓存)。所以,经过libaio,咱们能够向ceph集群提交大量的io,这样即可以测出集群的io极限。

6、关注笔者

专一笔者公众号,阅读更多干货文章:)

相关文章
相关标签/搜索