返回文章列表

文章

Rust Web开发 - 1

Rust异步编程

部分内容超过 Notion API 单页读取上限,已尽力加载可访问内容。

目录
  1. 5. 选择运行时
  2. 6. 选择Web框架
  3. 🧪 框架对比概览
  4. 🔍 深入分析
  5. 1. Actix Web
  6. 2. Axum
  7. 3. Rocket
  8. 4. Warp
  9. 5. Tide
  10. 🧭 选择建议
  11. 📎 参考文章
# 1. 同时处理多个请求 与其让每个线程完全处理每个请求,让另外成百上千个请求在等待处理,不如选择触发一个线程(例如数据库查询),在它处理完毕时得到通知,与此同时,该线程可以为其他客户端提供服务. 通过编写异步应用程序,你可以同时处理多个请求.在多线程环境中,可以把每个请求绑定在一个线程上,让他们在后台完成计算,然后再将他们放回前台,并把计算结果发回给请求的客户端. 另一个选择是使用单线程,再可能的情况下无限循环的去执行任务,有任务来就去处理.由于是单线程,它不能执行耗时很长的任务(如果执行耗时长的任务就会阻塞这个线程,类比死循环无限占用CPU),对于耗时长的任务,可以将这个任务的执行权交给**运行时**(runtime),并告诉运行时,这个任务需要很长时间去完成.运行时就知道去边执行其他任务,边检查这个耗时任务的是否完成运算. 要异步处理传入的HTTP请求,需要一种理解异步概念的编程语言,而且该语言应该提供相应的类型和语法,以便我们可以标记哪些是需要异步执行的代码.我们还需要一个运行时,它可以接受代码并知道如何以非阻塞方式执行代码(类比Nodejs和Go的runtime).如下图所示 ![异步编程环境的四个要素](https://prod-files-secure.s3.us-west-2.amazonaws.com/4b1b167f-4b52-4031-8233-f4f2689af574/68816f13-1bd6-4eb3-a3f1-e1ee98e9625a/image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466ZTZVWM46%2F20261016%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20261016T214532Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjECMaCXVzLXdlc3QtMiJIMEYCIQCZEFzIOgHGuW8vWbac1sKC2eBs7cMhf%2FTGIvIa9yP8VwIhAOIbF%2FzNvJ0IwxU9rbtS1nI9woqP7F%2BHzF3ty8%2FI1RoXKogECOv%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQABoMNjM3NDIzMTgzODA1IgwbFAkdE1ht7uubc1oq3ANn4QorpINp07nBt2BG3A3CxHDVj1xF%2Fm%2FqjyTf9ATDlnBuU2z7b%2FH3ZOV6OKAt9fdq8%2BsaJ9iSu3BT1b9pR9SnHN0b9G6LnLgFTlt7OCDWIv4RciOVr05RM64diWD3K6Ewi4D0NbJzDsA%2BFDf1ZS2rxKUc99DiCeoMNeUOqS3VL6BTwZRmDg72ofPDmmVRUUUdte%2FtjUDsuX9zy4Aq1gUKn6040YwI12%2FTV87d%2BIg0t5lB4TGByDKFrHHUwJ8Y5svHnrg8%2BGagbj90ISIu96ps%2FmAC2qC2aHvVgcJ3acVuMzHDM3bAbFvvoQIaK7304hypIr1%2Fpq5MOwyyq88dzq0Fj%2FodopKMXp189Z%2FKWrLPRvz6crpJcMrivrlKkqh6H%2F4S0J5B57RSJ3wWikeUQypV3Dv8ktTO1k1IeT5fLL4hUdAAR5GIicpNKU9TtaovNvcHkFnbbF7E1QOQhVe5QbZhjz91yJTMu%2BGmThiWbDBti29Rr4ZCUm8d%2BEdqyI18KeyaZ3LxhQnIe79If3NBY%2BiB0ZsVWVPB3mRHOqiJKJi%2F37q7W6kPzXUJPqXHU37zehZLFo7GZgacdfWDhLeKBaeplCsbejKz5d1wzi%2F9fx3oD6bv4S5JZz%2F%2F4RhnIjDP68nWBjqkAQoY%2Bgf1eRNT8zgx8Y9ANM5MIkvQNXPZhqpqgHEHY5xUFA9l%2FQj9g0tZzsrwZBC0UcI3nCMABABkxaxpJH3FslYmQHMjBXbe4rAOsWIKWYmo1FmCbVkphtt31an1I3baiKa97M6jmglvs1YKzAwHCFexthCdFmwCWz9gO0lNKQWdAs%2FVETYWZ%2FVaa64WdGGrB534NSOWeifpgGbXeLWRS1g4S%2BbW&X-Amz-Signature=4a18f75f26b1ecca8d14b8e987bc17ba52621b26fc7107afa02474b6964a0692&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject#notion_record=block.1d95c571-bb7d-8089-9583-d6199ff3a4b6.4b1b167f-4b52-4031-8233-f4f2689af574) 异步编程环境的四个要素总结如下: - [通过epoll/select/poll使用内核的异步读写API](https://jvns.ca/blog/2017/06/03/async-io-on-linux--select--poll--and-epoll/). - 能在用户空间中关闭长耗时任务,并在任务完成时发出通知,以便继续工作.这意味着运行时能创建和管理绿色线程. - 编程语言的语法中允许在代码块中标记异步块,使得编译器能够理解如何处理它们. - 标准库中的特定类型允许互斥访问和修改.与存储特定值的类型不同,异步编程类型需要在存储值的同时存储长耗时任务的当前状态. Rust提供了语法和类型.Rust本身支持异步概念,并提供了足够的组件来构建运行时对内核API进行抽象. # 2. Rust的异步环境 Rust中不包含任何运行时以及对内核异步API的抽象,这使得程序员有机会选择符合项目需求的运行时,也使得Rust在未来的运行时发生巨大进步时变得具有前瞻性. 下图展示了Rust异步编程的生态系统组件. ![](https://prod-files-secure.s3.us-west-2.amazonaws.com/4b1b167f-4b52-4031-8233-f4f2689af574/b7a25dac-e944-49db-86a4-6d9630be7521/image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466ZTZVWM46%2F20261016%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20261016T214532Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjECMaCXVzLXdlc3QtMiJIMEYCIQCZEFzIOgHGuW8vWbac1sKC2eBs7cMhf%2FTGIvIa9yP8VwIhAOIbF%2FzNvJ0IwxU9rbtS1nI9woqP7F%2BHzF3ty8%2FI1RoXKogECOv%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQABoMNjM3NDIzMTgzODA1IgwbFAkdE1ht7uubc1oq3ANn4QorpINp07nBt2BG3A3CxHDVj1xF%2Fm%2FqjyTf9ATDlnBuU2z7b%2FH3ZOV6OKAt9fdq8%2BsaJ9iSu3BT1b9pR9SnHN0b9G6LnLgFTlt7OCDWIv4RciOVr05RM64diWD3K6Ewi4D0NbJzDsA%2BFDf1ZS2rxKUc99DiCeoMNeUOqS3VL6BTwZRmDg72ofPDmmVRUUUdte%2FtjUDsuX9zy4Aq1gUKn6040YwI12%2FTV87d%2BIg0t5lB4TGByDKFrHHUwJ8Y5svHnrg8%2BGagbj90ISIu96ps%2FmAC2qC2aHvVgcJ3acVuMzHDM3bAbFvvoQIaK7304hypIr1%2Fpq5MOwyyq88dzq0Fj%2FodopKMXp189Z%2FKWrLPRvz6crpJcMrivrlKkqh6H%2F4S0J5B57RSJ3wWikeUQypV3Dv8ktTO1k1IeT5fLL4hUdAAR5GIicpNKU9TtaovNvcHkFnbbF7E1QOQhVe5QbZhjz91yJTMu%2BGmThiWbDBti29Rr4ZCUm8d%2BEdqyI18KeyaZ3LxhQnIe79If3NBY%2BiB0ZsVWVPB3mRHOqiJKJi%2F37q7W6kPzXUJPqXHU37zehZLFo7GZgacdfWDhLeKBaeplCsbejKz5d1wzi%2F9fx3oD6bv4S5JZz%2F%2F4RhnIjDP68nWBjqkAQoY%2Bgf1eRNT8zgx8Y9ANM5MIkvQNXPZhqpqgHEHY5xUFA9l%2FQj9g0tZzsrwZBC0UcI3nCMABABkxaxpJH3FslYmQHMjBXbe4rAOsWIKWYmo1FmCbVkphtt31an1I3baiKa97M6jmglvs1YKzAwHCFexthCdFmwCWz9gO0lNKQWdAs%2FVETYWZ%2FVaa64WdGGrB534NSOWeifpgGbXeLWRS1g4S%2BbW&X-Amz-Signature=d2a89b308016b65d35eb2ede53c6878534e6a45dda581a307f54083bde24599e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject#notion_record=block.1d95c571-bb7d-804d-9a90-e42a35c4163a.4b1b167f-4b52-4031-8233-f4f2689af574) Rust通过**`async`**/**`await`**关键字组合实现异步编程: - 语言层面提供**`async`**/**`await`**关键字组合 - 函数经**`async`**标记后,内部可使用**`await`** - **`await`**调用的函数返回**`Future`**类型,该类型包含: - 成功执行时的返回值类型 - **`poll`**方法(执行耗时操作并返回**`Pending`**或**`Ready`**状态) - **`Ready`**状态可携带错误或成功返回值 1. **开发者认知层级** - 基础应用:理解**`Future`**的存在意义及其生态交互即可 - 进阶开发:需掌握**`Future`**实现细节(如自主实现时) 1. **运行时选择策略** - 关键决策:为异步应用选择合适的运行时(runtime) - 标准运行时包含: - 内核API抽象层(通常基于Mio库实现) - 建议学习路径: 1. 首先掌握原生语法和类型系统 2. 再深入运行时机制 # 3. Rust处理`async`/`await` 关于`async/await` 的处理先看下面的代码 ```haskell use std::collections::HashMap;

#[tokio::main] async fn main() -> Result<(), Box> { // Send a GET request let response = reqwest::get("https://httpbin.org/ip") //这里会返回一个future .await? .json::<HashMap<String, String>>() .await?; println!("{:#?}", response); Ok(()) }

我们将main标记为`async` ,执行`reqwest::get("https://httpbin.org/ip")` 后会返回一个future,然后我们使用了`await`,这种语法使得异步的Rust代码看起来像是同步的,阻塞的代码(我们没有回调函数等等这些操作去处理future的结果).
其实在后台,Rust将这段代码转为一个状态机,其中每个不同的`await` (这段代码中使用了2个)
都代表一个状态,一旦所有状态都完成,函数将继续执行到最后一行代码并返回结果.这种处理的优点是代码简洁易于编写和理解,缺点也很明显,使得编写代码的人很难理解异步编程的本质和陷阱(当然对于那些喜欢开箱即用的开发者这可能是优点).下面我们可以看一下大名鼎鼎的future了 .
# 4. 使用Rust Future
在上一节提到,`reqwest::get("https://httpbin.org/ip")`执行后会返回一个Future,[Future](https://doc.rust-lang.org/stable/std/future/trait.Future.html)是一个比较复杂的类型,它是一个trait:
```haskell
pub trait Future {
    type Output;

    // Required method
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

Future 指的是可能尚未完成计算的值。这种“异步值”使得线程可以在等待该值可用时继续执行有用的工作. Future中的Output指的是完成后产生的值的类型,它可以是一个文件或者一个字符串. Future中的poll会返回一个Poll类型,Poll有两种状态Pending或者Ready,调用poll时,如果future没有准备好就会返回Pending,如果future完成了就会返回Ready(val),Ready携带着返回值. Rust中的Future和其他语言(例如Nodejs)中的是不同的,单独的Future是惰性的,不会主动去运行,运行时必须主动调用poll,更新Future的状态.

5. 选择运行时#

Tokio是Rust中最受欢迎的运行时,被广泛运用与整个行业.当我们遇事不决时,选Tokio总是没错的. 运行时的主要作用是创建线程.轮询更新各个future的状态,驱动它们完成作业.它还负责将任务传递给内核,并确保使用异步内核API以避免出现瓶颈.Tokio使用一个名为Mio(https://github.com/tokio-rs/mio)的库来与操作系统内核进行异步通信,作为开发人员,可以适当了解一下,在开发Rust的web应用时,你将那些类型的库和抽象层加入到了项目中. 如图所示,运行时在Web服务中起着相当重要的作用.它是业务代码和操作系的中间层,起着承上启下的作用.                             完整的Rust异步环境

6. 选择Web框架#

Web服务是Rust中的新领域,开发过程中难免会遇到很多问题和挑战,你可能需要开发团队和社区积极的帮助来解决你在探索过程中可能遇到的问题. 以下是 Rust 五大 Web 框架(Actix Web、Axum、Rocket、Warp、Tide)的详细特性对比,包括性能、学习曲线、功能特性、社区活跃度以及适用场景等方面:

🧪 框架对比概览#