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 拆出独立 cratepingora-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 高一些

下一步建议

  1. 跑一下官方 Quick Startdocs/quick_start.md 只需要 5 分钟,就能搭起一个负载均衡器
  2. 如果你在选型:先看 user guide,里面覆盖了配置、运行和自定义 HTTP 服务器的完整路径
  3. 如果你想深度理解架构:推荐阅读 DEV Community 的 Pingora Deep Dive,里面对两层池和 Server/Service 模型讲得很清楚
  4. v0.9.0 升级注意:MSRV 升到 1.85,部分 HTTP 头处理行为变化,Prometheus 集成迁移到了独立 crate,升级前看 changelog

Pingora 的核心价值主张不是”比 NGINX 快”,而是一个更现代的起点:用 Rust 的内存安全 + Tokio 的异步生态 + 可编程的 API,让你把代理当成软件来写,而不是配置文件来维护

GitHub:https://github.com/cloudflare/pingora(27,492 ⭐)

评论区

0 条评论

登录后可评论。