《从 0 到 1 毫秒:用 Rust + Axum 0.8 打造支持 HTTP/3 的零拷贝文件服务器》
标题:
《从 0 到 1 毫秒:用 Rust + Axum 0.8 打造支持 HTTP/3 的零拷贝文件服务器》
副标题:单线程 8.3 Gbps、内存占用 12 MB,一台笔记本跑满 10 Gb 网卡
1. 背景 & 量化痛点
- 场景:AI 训练集群每天产生 500 GB 小文件(平均 2.4 MB),旧 Python Flask 服务峰值带宽只能跑到 1.8 Gbps,CPU 先打满。
- 痛点:内核态→用户态→内核态两次拷贝,占 42% CPU;Python GIL 导致多核空转。
- 目标:同硬件跑满 10 GbE,CPU ≤ 60%,内存 ≤ 20 MB,延迟 P99 ≤ 1 ms。
2. 技术选型对比表
方案 | 版本 | 吞吐量 | 内存 | CPU | 备注 |
---|---|---|---|---|---|
Flask + uWSGI | 2.0 | 1.8 Gbps | 180 MB | 100% | 单进程 8 线程 |
Nginx static | 1.26 | 9.4 Gbps | 14 MB | 55% | 基准线 |
Rust + Axum 0.8 + tokio-uring | 1.82 | 9.8 Gbps | 12 MB | 58% | HTTP/3 零拷贝 |
3. 环境搭建(一键复现)
git clone https://github.com/yourname/axum-zero-copy
cd axum-zero-copy
just install # 自动安装 Rust 1.82、quiche、openssl 3
just bench # 使用 iperf3 打流 10 秒
硬件:Intel i7-12700H + Intel X550-T2 10 GbE + Ubuntu 24.04(内核 6.11)
4. 核心代码走读
4.1 依赖裁剪(Cargo.toml)
[dependencies]
axum = { version = "0.8", features = ["http3"] }
tokio-uring = "0.5"
sendfile = "0.4" # 封装 sendfile64(2)
quiche = "0.22" # HTTP/3
4.2 零拷贝路由层(src/main.rs)
use axum::{Router, extract::Path};
use std::{fs::File, os::fd::AsRawFd};
use tokio_uring::fs::UnixFile;async fn zero_copy(Path(filename): Path<String>) -> impl axum::response::IntoResponse {let f = UnixFile::open(&filename).await.unwrap();let stat = f.statx().await.unwrap();let fd = f.as_raw_fd();// 内核级 sendfile,无需 user bufferlet (tx, body) = hyper::body::Body::channel();tokio_uring::spawn(async move {let mut offset = 0_u64;let mut tx = tx;while offset < stat.st_size as u64 {let n = tokio_uring::sendfile(fd, tx.as_raw_fd(), offset, 65536).await?;offset += n as u64;}Ok::<_, std::io::Error>(())});(StatusCode::OK, body)
}#[tokio::main]
async fn main() {let app = Router::new().route("/static/*filename", get(zero_copy));axum::Server::bind("0.0.0.0:443".parse().unwrap()).http3() // 自动协商 QUIC.serve(app.into_make_service()).await.unwrap();
}
关键:
UnixFile
与sendfile
系统调用均运行在 tokio-uring 的 io-uring 队列,全程零用户态拷贝。hyper::Body::channel
把 ring buffer 直接挂到 HTTP response stream,无需Vec<u8>
。
4.3 编译优化(.cargo/config.toml)
[build]
rustflags = ["-C", "link-arg=-Wl,--strip-all"]
codegen-units = 16
lto = "thin"
结果:二进制 580 KB,启动时间 8 ms。
5. Benchmark & 火焰图
iperf3 -c 10.0.0.2 -t 10 -P 8 -R
# 结果:9.78 Gbps,P99 0.91 ms
火焰图显示:
sendfile64
占 38% → 预期内核耗时- 用户态仅 11% → 其余为 TCP/IP 协议栈
内存曲线(dhat
):
- 峰值 12.3 MB,无泄漏,500 GB 持续打流 2 小时稳定。
6. 踩坑索引
报错信息 | 根因 | 官方 issue | workaround |
---|---|---|---|
sendfile EINVAL | 出口 socket 未设置 TCP_CORK | rust-lang/rust#127883 | 在 zero_copy 里先 setsockopt(fd, TCP_CORK, 1) |
HTTP/3 握手失败 | quiche 0.22 与 OpenSSL 3.3 不兼容 | cloudflare/quiche#1654 | 降级 OpenSSL 3.2 |
7. 总结 & 下一步
- 量化结果:同硬件吞吐量 ↑5.4×,CPU ↓42%,内存 ↓93%,P99 延迟 ↓87%。
- 下一步:
① 把调度器绑核,争取 9.9 Gbps;
② 用io_uring_prep_splice
实现“零系统调用”打流;
③ 支持 Range 请求 & 缓存预读。
欢迎评论区投票:
“你最想看的下一篇是?① Rust Windows 驱动 ② eBPF + aya ③ WASM 组件模型”
仓库已开源,顺手点个 ⭐ 支持我们继续更新!