ublk:来自Linux社区的新热点,基于io_uring的全新高性能用户态块设备

简介: 如果您想快速了解ublk的意义、作用及性能,请直接看第二节Q&A部分。一、简介用户态块设备,就是提供/dev/ublkbX这样的标准块设备给业务,业务读写这个块的实际IO处理由您编写的用户态的代码决定。这就好比您使用FUSE,所有对挂载于FUSE的目录的读写都是您编写的IO handler来处理一样。使用用户态块设备,您可以方便地向上层业务以块设备/dev/ublkbX的形式提供您的自定义

如果您想快速了解ublk的意义、作用及性能,请直接看第二节Q&A部分。

一、简介

用户态块设备,就是提供/dev/ublkbX这样的标准块设备给业务,业务读写这个块的实际IO处理由您编写的用户态的代码决定。这就好比您使用FUSE,所有对挂载于FUSE的目录的读写都是您编写的IO handler来处理一样。使用用户态块设备,您可以方便地向上层业务以块设备/dev/ublkbX的形式提供您的自定义存储系统(如ceph)的服务,上层业务只需要对块设备执行标准的读写操作即可。

ublk是社区提出的基于最新的、流行的io_uring passthrough机制实现的用户态块设备,目前已经在block社区引起广泛讨论,并即将进入5.20内核主线。社区提供的代码包括内核态的ublk_drv.ko内核模块和用户态的ublksrv

        fio   target(backend): null/loop/qcow/socket/ceph/rpc...                             
    libublksrv                               user
----------------------------------------------------
              ublk_drv.ko   io_uring        kernel 

ublk_drv.ko是一个非常轻量级的内核模块:通过实现quque_rq()接口,获取来自blk-mq的IO请求;然后通过io_uring_passthrough机制把新的IO请求到来的通知回传给用户。真实的IO描述信息(如长度、opcode和flag等)由内核写入一块被用户态只读mmap()后的共享内存区域中,供用户态的ublksrv随后获取IO信息。

ublksrv是ublk的用户态部分,负责处理由内核发来的IO请求。ublksrv把IO请求交给:①注册的target(null、loop和qcow等)完成,每个target都按照各自的需求实现处理IO的接口,而ublksrv将自己作为daemon运行;②使用ublksrv的library API的自定义存储,ublksrv的逻辑嵌入到存储的IO处理框架(如ceph等大规模分布式系统)中,IO收发与处理运行在IO处理框架的上下文中。

目前,主流的用户态的块设备框架有nbd(nbdkit)和tcmu(tcmu-runner),nbd的性能与tcmu相近。我们特别调研了tcmu,它表现出了一些问题:

(1)tcmu使用SCSI协议,使得软件栈开销较大,我们使用fio iodepth=1, numjobs=1测试验证了这一事实。事实上,SCSI协议对于仅仅想要提供通用块设备的业务场景来说并不是必须的。 

(2)tcmu不支持多队列,没有利用blk-mq的特点;由于tcmu设计基于早年的uio框架,tcmu只能提供一个IO命令队列与唯一的fd供线程轮询(poll),使得多个io worker在分发IO请求时必须加全局锁;在io worker数量很大时,锁竞争的现象会很严重。 

(3)tcmu内部分配了data buffer,增加了额外的拷贝开销。以写请求为例,数据先复制到tcmu内部buffer,再复制到业务的后端IO框架的buffer(如RPC库的buffer);针对tcmu拷贝开销问题,Anolis社区给出了bypass和零拷贝等解决方案并开源。然而,我们的努力只是缓解了tcmu的拷贝问题,并没有解决tcmu的根本缺陷。

相比现有的tcmu、nbd等用户态块设备方案,ublk不受到任何协议的限制(如unix domain socket或SCSI协议),代码简洁软件栈开销很小;此外,ublk直接一一映射了blk-mq的多队列,每个ublk队列都包括一个io_uring实例互不干扰,不需要加锁同步任何信息,使得ublk的可扩展性优秀;最新的ublksrv还提供了library API,使得ublksrv嵌入到现有的IO处理框架中,使得这些框架提供/dev/ublkbX块设备直接使用;ublksrv支持data bypass机制,即允许直接把bio vectors的数据拷贝到业务后端的data buffer中,也将在未来支持异步dma、零拷贝或splice机制(目前上游社区暂无完美的零拷贝方案)。总之,ublk的潜力不容小觑。

我们在ublk开发初期便参与了社区讨论与代码设计,并及时测试新版本的性能。目前我们在block社区邮件列表对ublk进行特性贡献。ublk的性能也被我们完整地测试过。对比tcmu-runner,ublk在单IO的全链路时延、多线程场景IOPS和CPU开销上均有优势。测试数据详见后文Q&A部分。

二、Q&A

为了方便您快速了解ublk,我们自问自答了一些问题,也欢迎您评论提问。

Q1: 什么是用户态块设备,意义是什么?什么业务能用ublk?

A1: 用户态块设备就是提供一个/dev/ubdbX这样的块设备,所有这个设备上的IO请求的读写逻辑都是您在用户态编写的代码,比如pread/pwrite到一个具体地文件(这样就是loop设备了)。使用用户态块设备,你可以方便地向上层业务提供您的存储系统(如ceph),业务只需要对块设备执行标准的读写操作即可。如果您有一个用户态的存储系统(可以是跨多个机器的网络分布式存储),想要向业务暴露/dev/ublkbX块设备 ,提供IO读写功能的,就可以考虑ublk。比如您的业务用了容器或者虚拟机,就可以暴露/dev/ublkbX作为他们的磁盘,读写这些磁盘会导致实际IO passthrough(透传)给您的用户态存储系统(如ceph)。如果您没有存储系统,也可以使用ublk,因为ublksrv可以自己在本地运行daemon处理实际IO,如作为qcow target运行向容器或虚拟机提供qcow磁盘。

Q1: 现在ublk在linux社区是一个什么状态?你们现在ublk的支持怎样了,能否在现在的4.19或者5.10上跑起来?

A1: ublk利用了io_uring_passthrough新机制,是io_uring新特性的最佳实践之一,受到block与io_uring社区广泛关注。自ublk的idea提出以来,社区大佬们也赞誉有加。目前ublk已经合并在linux-block开发分支中,合并入5.20主线只是时间问题。 io_uring_passthrough机制加入需要修改内核本体,所以升级内核小版本是必须的。现在我们已经在Anolis社区给出了一个POC的用户态library内核(基于5.17)并测试(附在文后),您可以关注。适配Anolis 4.19或5.10内核的开发难度本身不大,如果您有适配ublk来POC的需求也欢迎找到我们讨论

Q3:感觉ublk和FUSE、SPDK或者NVMe over Fabrics很像?有没有什么对比?

A3:关于FUSE,它其实给你创建一个用户态文件系统的方法,你实现了后端的IO处理逻辑,就可以mount到一个目录让业务用了,其实和ublk是关注在不同存储层次(文件系统和块设备)的方案。

SPDK实际上bypass了整个内核,所以提供出支持POSIX API的块设备是困难的,您需要让业务使用SPDK的API才行,这样也许不太方便。此外SPDK也有CPU占用高、迁移成本大的问题(比如您必须把您的存储逻辑嵌入到SPDK中,而ublk提供的library允许您把ublk嵌入到您的存储逻辑中,后者改造代价更低)。

对于NVMf,我们认为那是另一种方案,通过网络提供远端NVMe存储,我觉得其实跟nbd更像一点。

Q5: ublk的一个IO的全链路是怎样的?

A5: 业务(如fio)向/dev/ublkbX发起IO请求,内核blk-mq模块通过queue_rq()向用户态ublksrv passthrough该IO请求,并填写映射的IO描述符信息;随后用户态ublksrv获取该IO信息并转交给特定的target(或者在library中)执行具体的IO处理(如loop设备的pread/pwrite);在IO完成后,ublksrv应通知给内核的blk-mq模块该IO已经完成。整个系统的框架如下:

                                   
                                   
                                target(backend)------------> null/loop/qcow/socket/ceph/rpc...  
                                      ^
                                      |
                                      |
                                   libublksrv
       fio                            ^
        |                             |        user
--------|-----------------------------|---------------
        |                             |        kernel
        v                             |
    /dev/ubdbX                        |        
        |                             |
        |                             |
        v                          io_uring      
      blk-mq                          ^   
        |                             |
         -----------> ublk_drv---------

Q6:ublk的性能如何?

A6: 对比了tcmu-runner和ublk的全链路时延和多线程IOPS

测试环境:

NVMe SSD: Intel DC P3600 Series 800GB, 

Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz,

Linux 5.17(加入io_uring uring_cmd支持)

测试选项:

测试使用fio,direct=1,bs=4k

ublksrv使用此处的代码,内核必须使用支持io_uring passthrough和ublk的版本

tcmu-runner的后端存储为user: fbo,一个对文件进行pread/pwrite的handler。我们修改了fbo的代码使其支持写入(本来只读)。

ublksrv后端是一个对文件进行pread/pwrite的backend,在此处您找到的ubd_test.c即为测试代码。

测试参数:

tcmu-runner的nr_rthreads是一个handler上的io worker的个数,您可以理解为有nr_rthreads个io worker不停地在一个SCSI队列上竞争拿取IO请求,每个串行地处理各自的IO请求(pread/pwrite)。

ublk的nr_queues是blk-mq的hardware queue的个数,hardware queue一一对应ublk的queue与其io_uring实例。ublk的queue互不干扰地拿取自己队列上的IO并串行地处理(pread/pwrite)。

测试数据:

  1. 单任务时延(usec) fio参数:iodepth=1, numjobs=1 tcmu-runner设置nr_rthreads=1,ublksrv设置nr_queues=1

Type

TCMU

UBLK

seq-read

38.47

24.50

rand-read

119.46

105.86

seq-write

38.45

26.48

rand-write

38.36

26.76

在4种类型的请求下,ublksrv的单任务时延均小于tcmu-runner,展示出ublk软件栈开销小的优势。

  1. 多任务IOPS(k) fio参数:iodepth=64, numjobs=4 tcmu-runner设置nr_rthreads=4,ublksrv设置nr_queues=4

Type

TCMU

UBLK

seq-read

32.7

41.3

rand-read

38.0

40.7

seq-write

161

202

rand-write

165

204

在4种类型的请求下,ublksrv的多任务IOPS均大于tcmu-runner,其中seq-write和rand-write优势明显(由于pwrite并未显著增加单个IO的时延),seq-read和rand-read的优势不大(由于pread是性能瓶颈,且ublksrv只是串行地执行队列上的所有IO)。

Q7:怎么感觉ublk没什么提升啊?rand-read提升不明显?

A7: 为了公平对比,在测试中只是简单地串行对每个IO执行pread/pwrite,没有进行任何优化(没有batch)。我们的测试只是简单的模拟,因此您会注意到ublk比tcmu的优势并不大,其实pread/pwrite阻塞执行拖后腿了。实际使用ublksrv时,后端IO处理的具体实现可以大幅度地改变IOPS。您可以把后端实现为aio/io_uring的批量提交与收割,或者批量把IO放入您的分布式存储的RPC接口。事实上我们也在此处实现了ubd_test_io_uring.c,在每个队列上一次性批量提交IO并等待全部完成,而非上面测试时简单地一个个pread/pwrite。我们注意到仅仅这种简单替换就使得ublk在iodepth=64, numjobs=4, nr_queues=4时的rand-read的IOPS提升到了250K。说明您后端的IO处理方式才影响性能,而非ublk本身。

Q8: 后面ublk还有什么支持?

A8: 异步dma、零拷贝、更完善的library和故障恢复,这些机制将确保最终ublk能胜任生产环境。整个更新至少持续一年。在保持跟随社区进展的同时,我们也会及时在ATA同步ublk的开发进展,希望大家多多关注ublk。

相关实践学习
RocketMQ一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
21天前
|
存储 缓存 Linux
Linux IO的奥秘:深入探索数据流动的魔法
Linux I/O(输入/输出)系统是其核心功能之一,负责处理数据在系统内部及与外界之间的流动。为了优化这一流程,Linux进行了一系列努力和抽象化,以提高效率、灵活性和易用性。🚀
Linux IO的奥秘:深入探索数据流动的魔法
|
1月前
|
存储 监控 Linux
【Linux IO多路复用 】 Linux下select函数全解析:驾驭I-O复用的高效之道
【Linux IO多路复用 】 Linux下select函数全解析:驾驭I-O复用的高效之道
54 0
|
2月前
|
缓存 运维 Linux
Linux系统调优详解(十二)——IO调优之磁盘测速
Linux系统调优详解(十二)——IO调优之磁盘测速
59 1
|
7天前
|
机器学习/深度学习 缓存 监控
linux查看CPU、内存、网络、磁盘IO命令
`Linux`系统中,使用`top`命令查看CPU状态,要查看CPU详细信息,可利用`cat /proc/cpuinfo`相关命令。`free`命令用于查看内存使用情况。网络相关命令包括`ifconfig`(查看网卡状态)、`ifdown/ifup`(禁用/启用网卡)、`netstat`(列出网络连接,如`-tuln`组合)以及`nslookup`、`ping`、`telnet`、`traceroute`等。磁盘IO方面,`iostat`(如`-k -p ALL`)显示磁盘IO统计,`iotop`(如`-o -d 1`)则用于查看磁盘IO瓶颈。
|
19天前
|
Linux
Linux操作系统调优相关工具(三)查看IO运行状态相关工具 查看哪个磁盘或分区最繁忙?
Linux操作系统调优相关工具(三)查看IO运行状态相关工具 查看哪个磁盘或分区最繁忙?
22 0
|
21天前
|
存储 缓存 安全
Linux IO:打开数据之窗的魔法
Linux I/O(输入/输出)是操作系统中一个至关重要的组成部分,它涉及到数据在内存🧠、存储设备💾、网络接口🌐等之间的传输过程。在Linux中,I/O操作不仅仅是文件读写那么简单,它包括了一系列复杂的机制和策略,旨在提高数据处理的效率,保证系统的稳定性和性能。📊
Linux IO:打开数据之窗的魔法
|
1月前
|
存储 缓存 Linux
探秘Linux块设备驱动程序:成为内核开发大师的第一步
探秘Linux块设备驱动程序:成为内核开发大师的第一步
95 0
|
1月前
|
NoSQL Java Linux
【Linux IO多路复用 】 Linux 网络编程 认知负荷与Epoll:高性能I-O多路复用的实现与优化
【Linux IO多路复用 】 Linux 网络编程 认知负荷与Epoll:高性能I-O多路复用的实现与优化
65 0
|
2月前
|
运维 安全 Linux
Linux系统调优详解(十一)——IO调优之文件打开数目限制
Linux系统调优详解(十一)——IO调优之文件打开数目限制
36 5