Monoio-Linux网络IO高性能异步运行时,字节跳动中国Rust之父。
大家好,我是梦兽编程。欢迎回来与梦兽编程一起刷Rust的系列。微信公众号【梦兽编程】即可加入梦兽编程微信交流群与我交流。
语言只是我们程序员实现途径中的工具,语言的生态丰富会让我们做起事来事半功倍。从今天开始我们开始进入Rust的生态学习。
如果将中国互联网的编程语言领域比作战国时代的版图,那么阿里巴巴在Java的领域就如同雄霸一方的秦国,占据了主导地位;而Golang则有着哔哩哔哩这样的文化重镇作为支撑,如同楚国以其文化繁荣著称;C++领域则由腾讯这样的强大势力守护,类比于齐国的军事强势。至于新兴的Rust语言,它的崛起和潜力无疑将由字节跳动来引领,正如赵国在战国后期的迅速崛起一样,字节跳动在Rust的推动上将发挥关键作用。
今天将会给大家推荐来着字节跳动的异步运行时Monoio
Monoio
Monoio 是一个开源的 Rust 运行时环境,专为异步 I/O 设计。它基于 io-uring 技术,这是一个新的 Linux 内核接口,用于提高 I/O 操作的性能。Monoio 的设计目标是提供一种简单、高效的方式来处理异步 I/O,特别是在网络编程和高性能服务器应用中。
计算机科普
在讲Monoio之前,我们需要先弥补一点计算机知识。为了做到异步并发,我们需要内核提供相关的能力,来做到在某个 IO 处于阻塞状态时能够处理其他任务。我们一般会使用epoll和io-uring,进行开发异步的能力。
epoll
是 Linux 内核中的一种 I/O 事件通知机制,通过提供一个文件描述符来监视多个文件描述符的 I/O 事件,如可读、可写和异常事件。它支持边缘触发(ET)和水平触发(LT)两种模式,可以有效地处理大量的文件描述符。
主要特点包括:
- 事件驱动的 I/O :
epoll
允许应用程序只在文件描述符准备好进行 I/O 操作时才进行操作,从而减少了不必要的系统调用和 CPU 使用。 - 高效的内存使用 :
epoll
不需要像select
和poll
那样每次调用时都传递所有的文件描述符,它只需要在初始化时传递一次,之后只需要传递发生变化的文件描述符。 - 可扩展性 :
epoll
可以处理大量的文件描述符,而不会像select
和poll
那样随着文件描述符数量的增加而性能下降。
io-uring
是 Linux 内核提供的一种新的高性能异步 I/O 框架,它是为了解决 epoll
和其他传统 I/O 方法的局限性而设计的。io-uring
通过提供一个更高效的方式来提交和完成 I/O 操作,从而提高了 I/O 性能。
主要特点包括:
- 提交和完成的批处理 :
io-uring
允许应用程序提交一批 I/O 操作,并在稍后一次性获取所有完成的结果,这样可以减少系统调用的次数。 - 零拷贝 I/O :
io-uring
支持零拷贝 I/O 操作,这意味着数据可以直接在用户空间和内核空间之间传输,而无需在两者之间进行拷贝。 - 改进的异步编程模型 :
io-uring
提供了一种更简单、更高效的异步编程模型,它通过一组特定的系统调用(io_uring_enter
等)来提交和完成 I/O 操作。 - 内置的轮询和通知机制 :
io-uring
内置了轮询和通知机制,可以更有效地处理 I/O 事件。
io-uring
的优势在于零拷贝,把数据拷贝到异步时的成本更低。
实现原理
我们先看图。
从这张图我们可以看出Monoio的实现逻辑有点类似消息队列
。这个队列是有单线程维护的,不会像Tokio那样充分利用多核。Redis也是单线程尽可能减少上下文切换带来的损耗。
Monoio具体可以分为以下四个点:
- 异步运行时:(如 Monoio)负责调度和执行这些
Future
。当一个Future
准备好继续执行时,它会通过一个Waker
对象来唤醒。 - **任务(Task):**一个任务(
Task
)是一个异步计算单元,它通常包含一个Future
和一个Waker
。任务可以在任何时候被挂起(当Future
返回Pending
时),并在稍后某个时刻(当Future
可以继续执行时)被唤醒。 - 调度器(Scheduler):调度器负责将任务放入运行队列,并在适当的时候执行它们。在
Monoio
中,由于采用了单线程模型,所有的任务都在同一个线程中执行,这意味着调度器不需要处理线程间的任务迁移。 - **Waker:**当一个
Future
由于等待某些资源(如 I/O 操作)而无法立即完成时,它会返回Pending
并注册一个Waker
。Waker
是一个通知机制,当资源准备好时,它可以唤醒关联的Future
,使其可以继续执行。在Monoio
中,Waker
的实现可能会与io-uring
的异步通知机制相结合。例如,当一个异步 I/O 操作开始时,Monoio 会将其注册到io-uring
的实例中,并设置一个Waker
。当 I/O 操作完成时,io-uring 会通过Waker
唤醒相应的Future
。
调度过程
- 任务提交 :异步任务(
Future
)通过.await
语法提交给运行时。 - 任务挂起 :如果任务由于等待 I/O 而无法继续执行,它会返回
Pending
并注册一个Waker
。 - I/O 操作 :Monoio 将 I/O 操作提交给 io-uring,并设置
Waker
以便在操作完成时接收通知。 - I/O 完成 :当 I/O 操作完成时,io-uring 通过
Waker
唤醒相应的任务。 - 任务恢复:被唤醒的任务重新进入运行队列,等待下一次调度。
- 任务执行:调度器从运行队列中取出任务并执行,直到任务完成或再次挂起。
性能考虑
Monoio
的任务调度考虑了性能优化。由于它基于单线程模型和 io-uring
技术,Monoio
可以在单个线程内高效地处理大量的异步 I/O 操作。这种设计减少了线程间切换的开销,并允许 Monoio
利用 io-uring
的高效批处理能力来提交和完成 I/O 操作。
快速入门
由于monoio兼容了tokio的api,所以直接替换即可。
rust
[dependencies]
monoio = "0.1"
rust
use monoio::runtime::Runtime;
#[monoio::main]
async fn main() {
// 异步代码
}
总结
Monoio
的任务调度是围绕 io-uring
的单线程模型和 Rust 的异步编程模型构建的。它通过优化异步任务的提交、挂起和唤醒过程,以及利用 io-uring
的高效 I/O 处理能力,实现了高性能的异步 I/O 操作。在实际应用中,这种调度策略使得 Monoio
成为处理 I/O 密集型任务的高效选择。
如果您对Rust异步编程感兴趣,欢迎关注我的公众号【梦兽编程】,一起探索Rust的奥秘。如果您觉得这篇文章对您有帮助,请分享给更多需要的朋友。您的转发是我最大的动力!