设计 Web 服务器
问题
如何用 Rust 设计一个高性能的 Web 服务器?
答案
架构设计
核心组件实现
简化的 Web 服务器核心
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::sync::Arc;
// 路由表:路径 → 处理函数
type Handler = Arc<dyn Fn(&Request) -> Response + Send + Sync>;
struct Router {
routes: Vec<(String, Handler)>,
}
impl Router {
fn route(&self, path: &str) -> Option<&Handler> {
self.routes.iter()
.find(|(p, _)| p == path)
.map(|(_, h)| h)
}
}
async fn run_server(addr: &str, router: Arc<Router>) -> std::io::Result<()> {
let listener = TcpListener::bind(addr).await?;
println!("Listening on {}", addr);
loop {
let (stream, _) = listener.accept().await?;
let router = router.clone(); // Arc clone 是廉价的
// 每个连接一个 Tokio task
tokio::spawn(async move {
if let Err(e) = handle_connection(stream, &router).await {
eprintln!("Connection error: {}", e);
}
});
}
}
关键设计决策
| 决策点 | 选择 | 原因 |
|---|---|---|
| 异步运行时 | Tokio | 最成熟,多线程 work-stealing |
| 并发模型 | 一连接一 task | Tokio task 轻量(~数百字节) |
| HTTP 解析 | httparse(零拷贝) | 不分配内存,直接引用缓冲区 |
| 路由 | Trie 树 | O(path_len) 匹配,支持参数路由 |
| 共享状态 | Arc<T> | 跨 task 共享,编译时保证安全 |
| Buffer | bytes::Bytes | 引用计数的零拷贝缓冲区 |
性能优化
- 零拷贝 IO:
tokio::io::copy直接在内核态传输数据 - 连接复用:HTTP/1.1 Keep-Alive,复用 TCP 连接
- backpressure:用
Semaphore限制并发连接数
use tokio::sync::Semaphore;
let sem = Arc::new(Semaphore::new(10000)); // 最大 1 万并发
loop {
let permit = sem.clone().acquire_owned().await?;
let (stream, _) = listener.accept().await?;
tokio::spawn(async move {
handle(stream).await;
drop(permit); // 释放许可
});
}
常见面试问题
Q1: 为什么 Rust Web 服务器性能高?
答案:
- 无 GC:没有 STW 暂停,延迟稳定
- 零拷贝:httparse 直接引用缓冲区,bytes::Bytes 共享数据
- 轻量 task:Tokio task 比 OS 线程轻量 1000 倍
- 编译优化:泛型单态化、内联、LTO
Q2: 如何处理 C10K/C100K 问题?
答案:
Tokio 的 epoll/kqueue + work-stealing 调度器天然支持高并发。每个连接一个 task(几百字节),10 万连接只需约几十 MB 内存。关键是避免阻塞:所有 IO 操作用 async,CPU 密集任务用 tokio::task::spawn_blocking。