跳到主要内容

设计 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
并发模型一连接一 taskTokio task 轻量(~数百字节)
HTTP 解析httparse(零拷贝)不分配内存,直接引用缓冲区
路由Trie 树O(path_len) 匹配,支持参数路由
共享状态Arc<T>跨 task 共享,编译时保证安全
Bufferbytes::Bytes引用计数的零拷贝缓冲区

性能优化

  • 零拷贝 IOtokio::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 服务器性能高?

答案

  1. 无 GC:没有 STW 暂停,延迟稳定
  2. 零拷贝:httparse 直接引用缓冲区,bytes::Bytes 共享数据
  3. 轻量 task:Tokio task 比 OS 线程轻量 1000 倍
  4. 编译优化:泛型单态化、内联、LTO

Q2: 如何处理 C10K/C100K 问题?

答案

Tokio 的 epoll/kqueue + work-stealing 调度器天然支持高并发。每个连接一个 task(几百字节),10 万连接只需约几十 MB 内存。关键是避免阻塞:所有 IO 操作用 async,CPU 密集任务用 tokio::task::spawn_blocking

相关链接