Cloudflare 跑了 8 年 NGINX 之后,用 Rust 重写了一个代理框架:18 个月踩坑实录
Cloudflare 跑了 8 年 NGINX 之后,用 Rust 重写了一个代理框架。18 个月、1200 万日活踩坑实录。
2022 年 Cloudflare 宣布用 Pingora 替换 NGINX 时,很多人觉得是过度工程。但当一支团队在 18 个月生产流量、每天 1200 万活跃用户的规模下真正做完这次迁移,他们掏出了一组让人无法忽视的数字:p99 延迟从 420ms 砍到 210ms,基础设施成本下降 35%,CVE 相关的中断事故从此消失。
Pingora 不是一个新项目,但 v0.9.0(2026 年 9 月 4 日刚发)把这个 Rust 框架带到了一个新的成熟度节点。
NGINX 的问题不是性能,是架构
为什么 Cloudflare 要换掉全世界最成熟的 Web 服务器?
NGINX 的 C 代码库在内存安全上天然存在风险。仅 2023 年一年,这个团队就被迫修了 9 个 CVE,其中 3 个直接破坏了他们的自定义 Lua 模块。每次打补丁都意味着完整重启代理,丢弃 2-3 秒的活跃连接——高峰时段这是不可接受的的中断。他们的滚动重启流程需要 45 分钟才能完成 48 台实例的轮换,期间 12 次出现部分中断。
更根本的问题在于架构本身。NGINX 是进程模型:每个 worker 是独立进程,一个 worker 无法使用另一个 worker 持有的空闲连接。强制建立新连接——这对需要复用连接池的 TLS 场景来说,是额外的握手开销。
Pingora 的答案:两层池 + 无锁热路径
Pingora 解决问题的思路很有意思。
它把所有线程放在同一个内存空间里,并设计了一套两层连接池:
- Lock-Free 热池(Hot Pool):每个线程本地持有空闲连接,因为是线程本地所以完全无锁,访问速度接近原始内存访问。底层用了
AtomicPtr——一种在 CPU 指令级别实现指针原子读写的类型。 - 全局共享池(Mutex):当热池溢出或为空时,访问带 Mutex 保护的全局池。
Pingora 用分层策略处理了 90% 的流量走热池、只有溢出才打 Mutex 这件事,兼顾了实际性能和灵活性。
另外,Pingora 还在 v0.9.0 重新设计了连接池的底层实现:引入了分片存储 + 真正全局 LRU,解决原有设计里过期条目和竞争窗口的问题。
架构抽象:Server / Service / ProxyHttp
Pingora 里有三个核心概念:
- Server:总 supervisor,处理信号、优雅关闭、Tokio Runtime 管理。用户不需要写
#[tokio::main],Server 内部自己初始化最优线程数和工作窃取设置。 - Service:具体任务。可以是”监听 80 端口的代理”,也可以是”定期聚合指标的后台任务”。
- ProxyHttp trait:写一个最小可运行代理,你需要实现 3 个方法——
type CTX(每个请求的内存状态)、new_ctx()(初始化这个状态)、upstream_peer()(决定把请求发到哪个后端)。
下面是一个可运行的最小代理示例(转发到 1.1.1.1 DNS):
use async_trait::async_trait;
use pingora::prelude::*;
pub struct MyProxy;
#[async_trait]
impl ProxyHttp for MyProxy {
type CTX = ();
fn new_ctx(&self) -> Self::CTX { () }
async fn upstream_peer(&self, _session: &mut Session, _ctx: &mut ()) -> Result<Box<HttpPeer>> {
let peer = Box::new(HttpPeer::new(
"1.1.1.1:443", true, "one.one.one.one".to_string(),
));
Ok(peer)
}
}
fn main() {
let mut my_server = Server::new(None).unwrap();
my_server.bootstrap();
let mut my_proxy_service = http_proxy_service(&my_server.configuration, MyProxy);
my_proxy_service.add_tcp("0.0.0.0:6188");
my_server.add_service(my_proxy_service);
my_server.run_forever();
}
v0.9.0 重要更新(2026-09-04)
这次大版本有几个值得注意的变化:
连接池重构:分片存储 + 全局 LRU 是最大的内部变化,解决了历史设计里的一些竞争条件和过期条目问题。
上游模块系统(Upstream Module System):可以在上游响应头收到之后、压缩之前插入自定义逻辑,做透明修改、请求染色、指标打点都很方便。
Prometheus 拆出独立 crate:pingora-prometheus 成为独立 crate,Prometheus 支持从必须变成可选,减少不必要的依赖。
TLS 能力增强:支持配置下游 TLS 握手的 offload 线程池,支持 runtime 构建的 rustls ServerConfig,支持 PROXY protocol 预 TLS 回调。
安全加固:大量 HTTP 解析边界情况处理、请求目标标准化、Hop-by-hop 头清理、默认 HTTP/2 限制收紧。
真实生产数据
一个跑了 18 个月的团队分享了完整数字(10k 并发连接,wrk2 测试):
| 指标 | NGINX 1.25 | Pingora(Rust 1.89) |
|---|---|---|
| p99 延迟 | 420ms | 210ms |
| 基础设施实例数 | 48 × m5.2xlarge | 31 × m5.2xlarge |
| 月度基础设施成本 | 基准 | -35% |
| CVE 相关中断(2023) | 12 次 | 0 次 |
背后有几个技术原因:Rust 1.89 对 ARM64 的内联汇编优化和 async/await 调度改进,将上下文切换开销减少了 62%;Pingora 的连接复用机制比 NGINX 进程模型高效得多。
适合谁 / 不适合谁
适合用 Pingora 的场景:
- 对内存安全有严格要求(C/C++ 代理的历史包袱)
- 需要高度定制代理逻辑(不想被迫写 Lua 或 NGINX config)
- 对延迟敏感、连接复用率高的场景(TLS、金丝雀发布)
- 需要在同一个进程里跑多个不同服务(而不是维护多个 NGINX 实例)
- 团队有 Rust 能力,或愿意学
不太适合的场景:
- 只是需要一个简单静态文件服务器——NGINX/OpenResty 更省事
- Windows 环境为主——Pingora 官方只保证 Linux 优先级,macOS 尽力而为,Windows 是社区尽力
- 需要快速原型验证——Pingora 的学习曲线比 NGINX 高一些
下一步建议
- 跑一下官方 Quick Start:docs/quick_start.md 只需要 5 分钟,就能搭起一个负载均衡器
- 如果你在选型:先看 user guide,里面覆盖了配置、运行和自定义 HTTP 服务器的完整路径
- 如果你想深度理解架构:推荐阅读 DEV Community 的 Pingora Deep Dive,里面对两层池和 Server/Service 模型讲得很清楚
- v0.9.0 升级注意:MSRV 升到 1.85,部分 HTTP 头处理行为变化,Prometheus 集成迁移到了独立 crate,升级前看 changelog
Pingora 的核心价值主张不是”比 NGINX 快”,而是一个更现代的起点:用 Rust 的内存安全 + Tokio 的异步生态 + 可编程的 API,让你把代理当成软件来写,而不是配置文件来维护。
GitHub:https://github.com/cloudflare/pingora(27,492 ⭐)
评论区
登录后可评论。