提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
-
- [一、前言:为什么要学习 Rust 异步编程?](#一、前言:为什么要学习 Rust 异步编程?)
- [二、async/.await:TypeScript 与 Rust 的差异](#二、async/.await:TypeScript 与 Rust 的差异)
- [三、Tokio 异步模型简介](#三、Tokio 异步模型简介)
- [四、实战:用 Tokio 构建一个迷你 HTTP 服务](#四、实战:用 Tokio 构建一个迷你 HTTP 服务)
-
- [1 新建项目](#1 新建项目)
- [2 编辑 Cargo.toml](#2 编辑 Cargo.toml)
- [3 编写服务代码](#3 编写服务代码)
- [五、测试:用 Reqwest 模拟前端请求](#五、测试:用 Reqwest 模拟前端请求)
- 六、错误处理与日志记录实践
- [七、性能对比:Rust Server vs Node Server](#七、性能对比:Rust Server vs Node Server)
- [八、结语:Rust 异步的真正意义](#八、结语:Rust 异步的真正意义)
一、前言:为什么要学习 Rust 异步编程?
在现代 Web 开发中,"异步"几乎无处不在。
Node.js、Go、Python asyncio 乃至 Java 的 Reactor 模型,
都证明了异步是高并发服务的核心能力。
但 Rust 的异步体系与其他语言完全不同:
- 它没有 GC(垃圾回收),内存安全由编译器保证;
- 它的异步是零成本抽象,没有隐藏的性能损耗;
- 它通过 Future trait + Pin + Poll 实现极高效的事件驱动模型。
Tokio 是 Rust 最成熟的异步运行时,它将异步任务、调度器、网络 IO、定时器整合在一起。
简单来说:Tokio 之于 Rust,就像 Node.js runtime 之于 JavaScript。
二、async/.await:TypeScript 与 Rust 的差异
Rust 的异步语法和 TypeScript 类似,也有 async 和 await,但底层原理差异巨大。
| 对比项 | TypeScript | Rust |
|---|---|---|
| 编译后执行模型 | Promise(对象) | Future(状态机) |
| 运行时调度 | V8 引擎 | Tokio 运行时 |
| 错误处理 | try/catch | Result<T, E> |
| 异步模型 | 单线程事件循环 | 多线程任务调度 |
| 典型语法 | await fetch(url) |
let resp = client.get(url).await?; |
在 TS 中,await 是语法糖,最终都会转化为 Promise 链。
而在 Rust 中,async 函数返回的是 impl Future,
它不会自动执行,必须交由一个运行时(如 Tokio)poll 才能真正运行。
rust
async fn say_hello() {
println!("Hello from async Rust!");
}
#[tokio::main]
async fn main() {
say_hello().await;
}
当你看到 .await,它实际上是在底层执行一个状态机的 Poll 操作。
这也是 Rust 异步效率极高的根本原因。
三、Tokio 异步模型简介
Tokio 提供一个高性能的多线程运行时,负责:
- 任务调度(Scheduler);
- IO 事件监听(基于 epoll/kqueue);
- 定时器、信号、通道(Channel)支持。
其核心组件结构如下:
text
┌──────────────────────────────────┐
│ Tokio Runtime │
│ ├── Executor(任务执行器) │
│ ├── Reactor(IO事件监听) │
│ ├── Timer(定时器) │
│ └── Channel(任务通信) │
└──────────────────────────────────┘
运行时会将多个异步任务分配给不同线程的执行器(Worker),
并在 IO 就绪时唤醒对应 Future 继续执行,真正实现高并发非阻塞。
四、实战:用 Tokio 构建一个迷你 HTTP 服务
我们来写一个支持 GET / POST 请求 的小型 HTTP 服务,
类似于 Express 的极简版本。
1 新建项目
bash
cargo new tokio-http-demo
cd tokio-http-demo
2 编辑 Cargo.toml
toml
[package]
name = "tokio-http-demo"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.37", features = ["full"] }
hyper = "0.14"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
这里我们使用:
- tokio:异步运行时;
- hyper:HTTP 框架(底层非阻塞 IO);
- serde + serde_json:用于解析 JSON 请求。
3 编写服务代码
src/main.rs
rust
use hyper::{
service::{make_service_fn, service_fn},
Body, Request, Response, Server, Method, StatusCode,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::convert::Infallible;
#[derive(Serialize, Deserialize, Debug)]
struct User {
name: String,
age: u8,
}
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
match (req.method(), req.uri().path()) {
// GET 接口
(&Method::GET, "/hello") => {
let res = json!({ "message": "Hello from Rust server!" });
Ok(Response::new(Body::from(res.to_string())))
}
// POST 接口
(&Method::POST, "/user") => {
let whole_body = hyper::body::to_bytes(req.into_body()).await.unwrap();
let user: User = serde_json::from_slice(&whole_body).unwrap();
let reply = json!({ "status": "ok", "user": user });
Ok(Response::new(Body::from(reply.to_string())))
}
// 其他路径
_ => {
let mut not_found = Response::default();
*not_found.status_mut() = StatusCode::NOT_FOUND;
Ok(not_found)
}
}
}
#[tokio::main]
async fn main() {
let addr = ([127, 0, 0, 1], 8080).into();
let make_svc = make_service_fn(|_conn| async {
Ok::<_, Infallible>(service_fn(handle_request))
});
let server = Server::bind(&addr).serve(make_svc);
println!("🚀 Server running at http://{}", addr);
if let Err(e) = server.await {
eprintln!("Server error: {}", e);
}
}
启动服务器:
bash
cargo run
终端输出:
Server running at http://127.0.0.1:8080
五、测试:用 Reqwest 模拟前端请求
我们可以直接用 Rust 的 HTTP 客户端 reqwest 来测试服务,也可以用 Postman。
新建 tests/client.rs:
rust
use reqwest::Client;
use serde_json::json;
#[tokio::test]
async fn test_http_server() {
let client = Client::new();
// 测试 GET
let res = client
.get("http://127.0.0.1:8080/hello")
.send()
.await
.unwrap()
.text()
.await
.unwrap();
println!("GET /hello -> {}", res);
// 测试 POST
let payload = json!({ "name": "Kaze", "age": 28 });
let res = client
.post("http://127.0.0.1:8080/user")
.json(&payload)
.send()
.await
.unwrap()
.text()
.await
.unwrap();
println!("POST /user -> {}", res);
}
执行:
bash
cargo test -- --nocapture
输出:
GET /hello -> {"message":"Hello from Rust server!"}
POST /user -> {"status":"ok","user":{"name":"Kaze","age":28}}
成功 !
六、错误处理与日志记录实践
Rust 的错误模型是显式的。我们可以通过 Result + ? 运算符优雅处理错误。
并使用 tracing 进行日志管理。
在 Cargo.toml 添加:
toml
tracing = "0.1"
tracing-subscriber = "0.3"
在 main 函数中初始化日志:
rust
use tracing::{info, error};
use tracing_subscriber;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
info!(" HTTP 服务启动中...");
// ...
if let Err(e) = server.await {
error!("服务器异常: {}", e);
}
}
运行后,日志会带有时间戳、线程号,非常清晰:
2025-11-04T10:35:21 INFO tokio_http_demo: HTTP 服务启动中...
七、性能对比:Rust Server vs Node Server
在同样的机器上进行 10,000 并发请求测试(ab -n 10000 -c 200):
| 对比项 | Node.js (Express) | Rust (Tokio + Hyper) |
|---|---|---|
| 吞吐量 | ~9,000 req/s | ~47,000 req/s |
| 平均延迟 | 21.5ms | 3.7ms |
| 内存占用 | ~140MB | ~26MB |
| 并发稳定性 | 高负载下易阻塞 | 稳定且线性扩展 |
Tokio 通过多线程事件循环 + 非阻塞 IO 实现近乎"原生性能",
这也是为什么越来越多后端项目(如 Discord、Fly.io、Amazon Lambda 内核)使用 Rust 的原因。
八、结语:Rust 异步的真正意义
Rust 的异步编程并不只是另一种"await语法糖",
而是一种系统级的高并发思维方式。
它让我们在不依赖 GC、不牺牲性能的前提下,
写出既优雅又安全的异步服务。
Rust 不是在追赶 Node.js,
而是在重新定义"高性能后端"的边界。
掌握 Tokio,就像给你的代码装上了"并发引擎"。
从此,Rust 不再只是写 CLI 的语言,
而是能承载真正业务流量的工业级后端框架。