likes
comments
collection
share

Monoio-Linux网络IO高性能异步运行时,字节跳动中国Rust之父

作者站长头像
站长
· 阅读数 4

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/Oepoll 允许应用程序只在文件描述符准备好进行 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/Oio-uring 支持零拷贝 I/O 操作,这意味着数据可以直接在用户空间和内核空间之间传输,而无需在两者之间进行拷贝。
  • 改进的异步编程模型io-uring 提供了一种更简单、更高效的异步编程模型,它通过一组特定的系统调用(io_uring_enter 等)来提交和完成 I/O 操作。
  • 内置的轮询和通知机制io-uring 内置了轮询和通知机制,可以更有效地处理 I/O 事件。

io-uring 的优势在于零拷贝,把数据拷贝到异步时的成本更低。

实现原理

我们先看图。

Monoio-Linux网络IO高性能异步运行时,字节跳动中国Rust之父

从这张图我们可以看出Monoio的实现逻辑有点类似消息队列。这个队列是有单线程维护的,不会像Tokio那样充分利用多核。Redis也是单线程尽可能减少上下文切换带来的损耗。

Monoio具体可以分为以下四个点:

  1. 异步运行时:(如 Monoio)负责调度和执行这些 Future。当一个 Future 准备好继续执行时,它会通过一个 Waker 对象来唤醒。
  2. **任务(Task):**一个任务(Task)是一个异步计算单元,它通常包含一个 Future 和一个 Waker。任务可以在任何时候被挂起(当 Future 返回 Pending 时),并在稍后某个时刻(当 Future 可以继续执行时)被唤醒。
  3. 调度器(Scheduler):调度器负责将任务放入运行队列,并在适当的时候执行它们。在 Monoio 中,由于采用了单线程模型,所有的任务都在同一个线程中执行,这意味着调度器不需要处理线程间的任务迁移。
  4. **Waker:**当一个 Future 由于等待某些资源(如 I/O 操作)而无法立即完成时,它会返回 Pending 并注册一个 WakerWaker 是一个通知机制,当资源准备好时,它可以唤醒关联的 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,所以直接替换即可。

[dependencies]
monoio = "0.1"
use monoio::runtime::Runtime;

#[monoio::main]
async fn main() {
    // 异步代码
}

总结

Monoio 的任务调度是围绕 io-uring 的单线程模型和 Rust 的异步编程模型构建的。它通过优化异步任务的提交、挂起和唤醒过程,以及利用 io-uring 的高效 I/O 处理能力,实现了高性能的异步 I/O 操作。在实际应用中,这种调度策略使得 Monoio 成为处理 I/O 密集型任务的高效选择。

如果您对Rust异步编程感兴趣,欢迎关注我的公众号【梦兽编程】,一起探索Rust的奥秘。如果您觉得这篇文章对您有帮助,请分享给更多需要的朋友。您的转发是我最大的动力!

转载自:https://juejin.cn/post/7353459702929588287
评论
请登录