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版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
2月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
98 0
|
1月前
|
安全 Linux 虚拟化
|
2月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
106 1
Linux C/C++之IO多路复用(aio)
|
2天前
|
Ubuntu Linux Shell
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
(已成功解决)Linux环境报错—bash: wget: command not found;常见Linux发行版本,Linux中yum、rpm、apt-get、wget的区别;Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
|
4月前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
2月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
36 0
Linux C/C++之IO多路复用(poll,epoll)
|
4月前
|
存储 缓存 Linux
Linux源码阅读笔记15-块设备详解
Linux源码阅读笔记15-块设备详解
|
5月前
|
存储 JSON Linux
|
4月前
|
缓存 监控 Linux
在Linux中,如何设计一个高性能的Web服务器?
在Linux中,如何设计一个高性能的Web服务器?
|
4月前
|
小程序 Linux 开发者
Linux之缓冲区与C库IO函数简单模拟
通过上述编程实例,可以对Linux系统中缓冲区和C库IO函数如何提高文件读写效率有了一个基本的了解。开发者需要根据应用程序的具体需求来选择合适的IO策略。
38 0