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、關注筆者

專一筆者公衆號,閱讀更多幹貨文章:)

相關文章
相關標籤/搜索