尊重原创版权: https://www.conghengx.com/hot/37285.html
更多内容参考: https://www.conghengx.com/
io_uring是 2019 年 Linux 5.1 内核首次引入的高性能 异步 I/O 框架 ,能显着加速 I/O
密集型应用的性能。但如果你的应用 已经在使用 传统 Linux AIO 了, 并且使用方式恰当 , 那io_uring
并不会带来太大的性能提升 —— 根据测试,即便打开高级特性,也只有 5%。除非你真的需要这 5% 的额外性能,否则 切换成io_uring
代价可能也挺大 ,因为要重写应用来适配io_uring(或者让依赖的平台或框架去适配,总之需要改代码)。
既然性能跟传统 AIO 差不多,那为什么还称 io_uring 为革命性技术呢?
1、它首先和最大的贡献在于: 统一了 Linux 异步 I/O 框架 ,
2、在 设计上是真正的异步 I/O ,作为对比,Linux AIO 虽然也 是异步的,但仍然可能会阻塞,某些情况下的行为也无法预测;
3、灵活性和可扩展性非常好,甚至能基于 io_uring 重写所有系统调用,而 Linux AIO 设计时就没考虑扩展性。
eBPF 也算是异步框架(事件驱动),但与 io_uring 没有本质联系,二者属于不同子系统, 并且在模型上有一个本质区别:
eBPF 作为动态跟踪工具,能够更方便地排查和观测 io_uring 等模块在执行层面的具体问题。
本文介绍 Linux 异步 I/O 的发展历史,io_uring 的原理和功能, 并给出了一些 程序示例和 性能压测结果(我们在 5.10
内核做了类似测试)。
以下是译文。
很多人可能还没意识到,Linux 内核在过去几年已经发生了一场革命。这场革命源于 两个激动人心的新接口的引入: eBPF 和
io_uring 。我们认为,二者将会完全 改变应用与内核交互的方式 ,以及 应用开发者思考和看待内核的方式 。
本文介绍 io_uring(我们在 ScyllaDB 中有 io_uring 的深入使用经验),并略微提及一下 eBPF。
作为大家最熟悉的读写方式,Linux 内核提供了 基于文件描述符的系统调用 , 这些描述符指向的可能是 存储文件 (storage
file),也可能是 network sockets :
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
二者称为 阻塞式系统调用 (blocking system calls),因为程序调用 这些函数时会进入 sleep
状态,然后被调度出去(让出处理器),直到 I/O 操作完成:
但很容易想到,随着存储 设备越来越快,程序越来越复杂 , 阻塞式(blocking)已经这种最简单的方式已经不适用了。
阻塞式之后,出现了一些新的、非阻塞的系统调用,例如 select()、poll() 以及更新的 epoll()。应用程序在调用这些函数读写时不会阻塞,而是
立即返回 ,返回的是一个 已经 ready 的文件描述符列表 。
![Linux 异步 I/O 框架io_uring:基本原理、程序示例与性能压测](https://p9.toutiaoimg.com/origin/tos-cn-i-
qvj2lq49k0/46ff4ac87744410599e5536e10a0196e?from=pc)
但这种方式存在一个致命缺点: 只支持 network sockets 和 pipes ——epoll() 甚至连 storage files
都不支持。
对于 storage I/O,经典的解决思路是 thread pool[5] :主线程将 I/O 分发给 worker
线程,后者代替主线程进行阻塞式读写,主线程不会阻塞。
![Linux 异步 I/O 框架io_uring:基本原理、程序示例与性能压测](https://p9.toutiaoimg.com/origin/tos-cn-i-
qvj2lq49k0/1848e0c077c04cf98b04f9ef70f1cedd?from=pc)
这种方式的问题是 线程上下文切换开销可能非常大 ,后面性能压测会看到。
随后出现了更加灵活和强大的方式: 数据库软件 (database software) 有时 并不想使用操作系统的 page cache[6]
, 而是希望打开一个文件后, 直接从设备读写这个文件 (direct access to the device)。这种方式称为 直接访问
(direct access)或 直接 I/O (direct I/O),
前面提到,随着存储设备越来越快,主线程和 worker 线性之间的上下文切换开销占比越来越高。现在市场上的一些设备,例如 Intel
Optane[7] , 延迟已经低到和上下文切换一个量级 (微秒 us)。换个方式描述, 更能让我们感受到这种开销:
上下文每切换一次,我们就少一次 dispatch I/O 的机会 。
因此,Linux 2.6 内核引入了异步 I/O(asynchronous I/O)接口, 方便起见,本文简写为 linux-aio。AIO
原理是很简单的:
近期, Linux AIO 甚至支持了[8] epoll():也就是说 不仅能提交 storage I/O 请求,还能提交网络 I/O
请求。照这样发展下去,linux-aio 似乎能成为一个王者 。但由于它糟糕的演进之路,这个愿望几乎不可能实现了。我们从 Linus
标志性的激烈言辞中就能略窥一斑 :
Reply to: to support opening files asynchronously[9] So I think this is
ridiculously ugly. AIO is a horrible ad-hoc design, with the main excuse
being “other, less gifted people, made that design, and we are implementing
it for compatibility because database people — who seldom have any shred of
taste — actually use it”. — Linus Torvalds (on lwn.net)
首先,作为数据库从业人员,我们想借此机会为我们的没品(lack of taste)向 Linus 道歉。但更重要的是,我们要进一步解释一下 为什么
Linus 是对的 :Linux AIO 确实问题缠身,
以上可以清晰地看出 Linux I/O 的演进:
另外也看到,在非阻塞式读写的问题上 并没有形成统一方案 :
这就是 Linux I/O 的演进历史 —— 只着眼当前,出现一个问题就引入一种设计,而并没有多少前瞻性 —— 直到 io_uring 的出现。
相关视频推荐
io_uring 新起之秀的linux
io