Cloudflare 公开 Pingora 时,最抓眼球的数字是:在相同流量下,新的代理系统比旧系统少用了约 70% CPU 和 67% 内存。
看到这个数字,很多人的第一反应可能是:是不是 Nginx 不行了?是不是 Rust 天生就比 C 快很多?
都不是。
我们真正要回答的是:Cloudflare 为什么会觉得原来的 NGINX/OpenResty 代理不够用了?Pingora 又改掉了哪些最贵的地方?
后面就顺着这个问题往下走。先看 Pingora 是什么,再看 Nginx 在普通场景里为什么很好用,最后再看 Cloudflare 这种规模下,连接池、线程模型和业务逻辑会怎样把成本放大。
可以先带着三句话往下读,后面会一层层展开:
- Cloudflare 替换的是他们内部深度改造过的 NGINX/OpenResty 代理,不是说 Nginx 在所有场景下都慢。
- Pingora 开源版更像“用 Rust 写代理服务的工具箱”,不是一个直接读取
nginx.conf的 Nginx 替代品。 - 重点不是“Rust 打败 Nginx”,而是连接复用、线程共享和复杂代理逻辑这些具体问题。
1. Pingora 不是一个新的 Nginx
如果你平时用 Nginx,可能会自然地把 Pingora 想成“另一个服务器软件”。但这样理解会有点偏。
Pingora 更像一套用 Rust 写代理服务的工具箱。它帮你处理连接、TLS、HTTP 协议、负载均衡、连接池、优雅升级这些通用能力;你的业务逻辑则写在 Rust 回调里。
它不是单个二进制 Web 服务器,而是一组 crate:
| crate | 作用 |
|---|---|
pingora-core | 底层协议、服务运行、监听器、连接器、优雅升级 |
pingora-proxy | HTTP 代理生命周期和 ProxyHttp 回调接口 |
pingora-pool | 高并发连接池 |
pingora-runtime | Tokio runtime 封装,支持 work-stealing 与 no-steal 模式 |
pingora-load-balancing | 负载均衡、健康检查、选择算法 |
pingora-cache / pingora-memory-cache | 缓存相关能力 |
这些名字不用急着都记住。先有一个印象就够了:Pingora 把“写代理服务”拆成了一组可以组合的 Rust 库。
Cloudflare 在 2024 年 2 月开源 Pingora,并说明它已经在 Cloudflare 全球网络中支撑了大量流量。GitHub README 里也提到,Pingora 已经长期服务超过 4000 万请求/秒的生产流量。到 2026 年 5 月 4 日,Cloudflare 又在 changelog 中说明,Cloudflare cache 也已经迁移到基于 Pingora 的新代理上。
这个背景想说明的不是“大家都该换 Pingora”,而是:它不是实验性质的小项目,而是从 Cloudflare 自己的生产问题里长出来的。
为了避免一上来就掉进 crate 名字里,可以先看它的编程方式。一个非常简化的代理大概长这样:
struct Gateway;
#[async_trait]
impl ProxyHttp for Gateway {
type CTX = RequestContext;
fn new_ctx(&self) -> Self::CTX {
RequestContext::default()
}
async fn request_filter(
&self,
session: &mut Session,
ctx: &mut Self::CTX,
) -> Result<bool> {
// 鉴权、限流、路由预处理、直接返回响应
Ok(false)
}
async fn upstream_peer(
&self,
session: &mut Session,
ctx: &mut Self::CTX,
) -> Result<Box<HttpPeer>> {
// 为当前请求选择 upstream
Ok(peer)
}
async fn response_filter(
&self,
session: &mut Session,
response: &mut ResponseHeader,
ctx: &mut Self::CTX,
) -> Result<()> {
// 改响应头、打点、策略处理
Ok(())
}
}
这套接口有点像 OpenResty 的各阶段回调:请求进来时做校验,选择 upstream,响应回来时改响应头,最后记录日志。
区别是,业务逻辑不再在 Nginx C core 和 Lua VM 之间来回切换,而是直接写在 Rust 里。连接池、TLS、HTTP 解析、读写、超时、重试、优雅关闭这些通用代理能力,由框架帮你处理。
2. 再看 Nginx:它为什么稳,也为什么会卡在连接复用上
Nginx 的经典架构是:一个 master 进程管理多个 worker 进程,每个 worker 是单线程事件循环。
这个设计很稳。进程之间隔离,一个 worker 出问题,不容易把整个服务一起拖垮。对静态文件、普通反向代理、边缘入口这类场景来说,Nginx 仍然非常合适。
问题出在 Cloudflare 这类场景:请求量巨大,后端 origin 很多,而且非常依赖 upstream 连接复用。
这时,Nginx 多 worker 的设计会带来一个不太直观的代价:
- 每个 worker 都有自己的地址空间。
- 每个 worker 都维护自己的 upstream keepalive 连接。
- 一个请求落到哪个 worker,通常就只能复用那个 worker 手里的连接。
这不是 Nginx “写得差”,而是多进程模型本来就是这样工作的。
Nginx 官方文档对 upstream keepalive 的描述很直接:connections 参数保存的是“每个 worker 进程”里的空闲 upstream keepalive 连接。
换句话说,如果一台机器上有 16 个 worker,它不是拥有一个大家共享的连接池,而是有 16 份彼此看不见的小池子。
源码上也能看到这个模型。ngx_http_upstream_keepalive_module.c 里,keepalive 模块维护的是当前 worker 内部的 cache / free 队列;请求释放 upstream 连接时,连接被挂回当前 worker 的队列;后续只有同一 worker 再处理到同类 upstream 请求时,才能直接复用它。
这个细节在普通业务里通常没问题,但在 Cloudflare 这种规模下会被放大:
- 请求量极大。
- origin 数量极多。
- TLS 握手成本高。
- 不同请求被内核和 Nginx 调度到不同 worker。
- 单个客户或单个 origin 的流量可能高度突发。
最后就会出现一种很浪费的情况:机器上明明已经有到某个 origin 的空闲 TLS 连接,但新请求落在另一个 worker,它还是可能重新建 TCP 连接、重新做 TLS 握手。
3. 关键变化:连接池可以跨线程共享
上一节的问题,其实就是 Pingora 最重要的改动入口。
Cloudflare 官方文章里提到,Pingora 的 TTFB 改善不是因为旧代码单个请求处理得很慢。旧服务本身也能在亚毫秒级完成处理。真正的收益来自新架构能跨线程共享连接,从而大幅提高连接复用率。
落到 Pingora 源码里,对应的就是 pingora-pool。
pingora-pool/src/connection.rs 里有三个关键概念:
| 结构 | 核心含义 |
|---|---|
ConnectionMeta | 描述连接的分组 key 和连接 id,同一个 key 下的连接可互换复用 |
PoolNode | 某个 key 对应的一组连接 |
ConnectionPool<S> | 全局连接池,按 group key 找到对应 PoolNode,并用 LRU 控制池大小 |
简化后的结构关系可以理解成:
ConnectionPool
-> RwLock<HashMap<GroupKey, Arc<PoolNode>>>
-> Lru<ConnectionId, ConnectionMeta>
PoolNode
-> hot_queue: lock-free ArrayQueue
-> fallback: Mutex<HashMap<ConnectionId, Connection>>
这里不用急着记结构名,抓住几个点就够了。
第一,连接按 GroupKey 分组。对 HTTP 代理来说,这个 key 可以代表“同一个 upstream 目标及其连接属性”。只要 key 相同,连接就可以被另一个请求拿走复用。
第二,连接池被设计成线程安全结构。Pingora 是多线程单进程模型,业务逻辑、连接池和共享状态可以放在 Arc 后面被多个线程访问。线程 A 释放的 upstream 连接,可以被线程 B 取走。
第三,PoolNode 有一个很小的 lock-free hot queue。高频复用的连接会优先走这个热队列,减少在高 RPS 下反复抢同一个互斥锁。只有 hot queue 满了,才落到带锁的 HashMap。
第四,连接池有 LRU 淘汰。池子不是无限增长,超出容量时淘汰最久未使用的连接,避免“为了复用而把 origin 打爆”。
再往 HTTP 层看,HttpSession::reuse() 的语义是:如果当前 HTTP/1 连接仍然可复用,就把底层 stream 取出来交还给连接池;否则关闭连接。TransportConnector 的文档也明确说明它负责通过 TCP/TLS 建连,并支持 connection reuse。
把 Nginx 和 Pingora 的连接复用路径并排看,差异会更直观:
Nginx/OpenResty:
request -> worker N -> worker N 的 upstream keepalive cache
-> 命中:复用
-> 未命中:重新 TCP + TLS
Pingora:
request -> 任意线程 -> 共享连接池
-> 同 group key 命中:复用
-> 未命中:重新 TCP + TLS
Cloudflare 给出的生产数据也正好对应这个机制:迁移到 Pingora 后,整体新建连接数降到旧服务的三分之一;某个大客户的连接复用率从 87.1% 提升到 99.92%,新建 origin 连接减少了 160 倍。TTFB 中位数降低 5ms,P95 降低 80ms。
这就是为什么“共享连接池”很关键。TCP/TLS 握手比在已有连接上收发数据贵得多。少建连接,就意味着少烧 CPU、少等 RTT、少占内存。
4. 线程模型:重点不是神奇调度,而是少一些 worker 隔离
聊完连接池,再看线程模型就顺了。
很多文章会把 Pingora 的性能归因于 Tokio work-stealing。这个说法有一部分对,但如果只这么说,会让人误以为“换个调度器就快了”。
Nginx worker 是多进程单线程模型。某个连接、某个请求、某个 Lua 执行阶段落在哪个 worker,基本就由哪个 worker 处理。Cloudflare 官方文章提到,这种 request-process pinning 会带来 CPU 核心之间负载不均的问题:一个 worker 被 CPU-heavy 或阻塞 I/O 拖慢时,其他 worker 未必能帮它处理队列里的请求。
Pingora 的运行模型不同。pingora-runtime 把 Tokio runtime 封装成两类:
Steal:普通 Tokio 多线程 runtime,也就是带 work-stealing 的调度器。NoSteal:Pingora 自己封装的多个 current-thread Tokio runtime,用来减少 work-stealing 的调度开销,同时仍然利用多核。
这说明 Pingora 并不是简单地相信“work-stealing 一定最快”。它真正提供的是一个更灵活的多线程执行模型:业务可以在同一个进程内跨线程共享状态,运行时也可以根据场景选择是否启用 work stealing。ServerConf 里甚至直接暴露了 threads、work_stealing、upstream_keepalive_pool_size 等配置项。
因此,这一层带来的性能改善可以概括为:
- Nginx 的 worker 之间隔离,调度和状态共享都受限。
- Pingora 的线程之间可共享连接池和业务状态。
- Tokio runtime 能把大量异步 I/O 任务调度到多个核心。
- 对特定场景,Pingora 还能用 no-steal runtime 降低调度器自身开销。
所以这一节的重点不是某个调度算法有多神,而是 Pingora 少了很多 worker 之间互相看不见、帮不上忙的问题。
5. 业务逻辑:少在 C 和 Lua 之间来回搬数据
如果只是普通反向代理,连接池可能已经解释了很大一部分收益。但 Cloudflare 的旧系统不是“只跑 Nginx 配置”。
它在 NGINX/OpenResty 上承载了大量复杂逻辑:路由、鉴权、安全策略、缓存控制、产品特性等。
OpenResty 的强大之处在于把 Lua 嵌进 Nginx 生命周期,让用户可以在不同阶段写脚本。但这也带来一个天然成本:底层 HTTP 对象在 C 结构体里,上层业务逻辑在 Lua VM 里。
Cloudflare 官方文章举了一个具体例子:Lua 代码访问 HTTP header 时,需要从 Nginx C 结构体读出、分配 Lua string、拷贝过去,之后还要交给 Lua GC 回收。共享内存也有类似限制:旧体系中共享内存访问需要 mutex,而且只能放字符串和数字。
Pingora 的 ProxyHttp 则把这些业务阶段直接变成 Rust trait 方法:
request_filter():请求头到达后做校验、限流、鉴权,或者直接返回。upstream_peer():选择 upstream。upstream_request_filter():发往 upstream 前改请求。upstream_response_filter()/response_filter():处理 upstream 响应和下游响应。logging():请求结束后的指标和日志。
也就是说,原来可能跨越 Nginx C core、Lua VM、共享字典、FFI glue 的逻辑,现在可以在一个 Rust 类型系统里完成。
性能收益不只来自 Rust 编译成本地代码,还来自少分配、少拷贝、少 GC、少跨语言上下文切换。
这也是 Pingora 对 Cloudflare 特别有价值的原因:它不是只代理几个固定 upstream,而是承载大量产品逻辑的可编程代理平台。
6. Rust 的价值:让团队敢写更复杂的共享结构
讲到这里,再看 Rust 就比较合理了。
Rust 的内存安全经常被拿来解释 Pingora 的性能,但它不是直接让 CPU 指令变快。它更重要的作用,是让团队更敢于写复杂的共享结构。
在 C 里写一个高并发连接池、共享缓存、跨线程状态系统,需要工程师长期和 use-after-free、double free、数据竞争、悬垂指针打交道。为了稳定性,很多设计会天然保守。
Rust 的所有权、借用检查和 Send / Sync 约束让 Pingora 可以把“连接能不能跨线程移动”“引用能不能跨线程共享”这类问题提前放到编译期检查。比如连接池里的对象要跨线程复用,就必须满足相应的 trait 约束;共享状态放进 Arc、锁、原子类型背后,数据竞争会被类型系统拦下。
这不代表 Pingora 就不会有 bug。HTTP request smuggling、缓存 key 设计、协议兼容性这些仍然要靠严谨实现和测试。
但 Rust 至少把一大类内存破坏和数据竞争问题从运行时事故变成了编译期约束。对一个需要高并发连接池、共享缓存、跨线程状态的代理系统来说,这一点很重要。
7. Pingora 做了什么取舍
Nginx 是一个非常通用的系统:
- 静态文件服务器。
- 反向代理。
- 负载均衡。
- FastCGI / uwsgi / SCGI。
- 邮件代理。
- 大量第三方模块生态。
通用性是 Nginx 的优势。但 Cloudflare 的 origin-facing proxy 需要的是另一种东西:一个能被内部产品深度编程、能高效复用 origin 连接、能持续演进协议和缓存能力的代理平台。
Pingora 的取舍更聚焦:
- 不追求成为
nginx.conf的兼容替代品。 - 把核心抽象放在 Rust API 上,而不是配置 DSL 上。
- 把高并发连接复用、代理生命周期、TLS、HTTP/1/2、负载均衡、观测能力做成可组合库。
- 让 Cloudflare 可以快速加产品特性,比如 HTTP/2 upstream、gRPC、Cache Reserve,以及 2026 年迁移到 Pingora 的新 cache proxy。
所以 Pingora 的高性能不是某一个神奇算法,而是几个选择叠加后的结果:
性能收益
= 更高连接复用率,减少 TCP/TLS 握手
+ 多线程共享状态,减少 worker 孤岛
+ Rust 原生业务逻辑,减少 C/Lua 边界和 GC 成本
+ 更适合 Cloudflare 代理需求的协议与缓存架构
+ 内存安全带来的并发优化空间
8. 你该不该用 Pingora?
如果你的需求是:
- 托管静态文件。
- 简单反向代理几个服务。
- 依赖成熟配置生态。
- 需要大量现成模块。
- 团队不想写 Rust 代码。
那 Nginx 仍然是非常合理的选择。它稳定、成熟、文档多、运维经验丰富。
如果你的需求是:
- 网关逻辑非常复杂。
- 需要把代理深度嵌入业务系统。
- 对 upstream 连接复用和延迟极度敏感。
- 希望用 Rust 写原生代理逻辑。
- 想构建自己的 API gateway、CDN proxy、service mesh sidecar、可编程负载均衡器。
那 Pingora 更值得考虑。
换句话说,Nginx 是成熟的通用服务器,Pingora 是现代化的可编程代理底座。
Cloudflare 能得到巨大收益,是因为它的问题正好集中在几个地方:worker 之间连接不能共享,复杂逻辑要穿过 C/Lua 边界,代理系统还要长期承载大量产品能力。
9. 总结
Pingora 比 Cloudflare 旧 NGINX/OpenResty 体系高效很多,核心不是“Rust 天生比 C 快”,更不是“Nginx 慢”。
把整篇文章压缩成一句话就是:Cloudflare 原来的代理系统在连接复用、状态共享和复杂业务逻辑上付出了很高成本,而 Pingora 针对这些成本重新设计了一套 Rust 代理框架。
具体来说:
- Nginx upstream keepalive 是每 worker 独立缓存,连接复用率会被 worker 数量和流量分布稀释。
- Pingora 通过多线程单进程模型和线程安全连接池,让 upstream 连接可以跨线程复用。
- Pingora 的连接池源码针对高并发做了 hot queue、LRU、idle watcher 等设计。
- Pingora 避免了 OpenResty 场景下 C/Lua 数据转换、字符串分配、GC、共享内存 mutex 等额外成本。
- Rust 类型系统降低了复杂并发共享结构的实现风险,让团队可以更放心地做并发优化。
在中小规模、通用 Web 场景下,Nginx 仍然是非常优秀的选择。到了 Cloudflare 这种每秒数千万请求、海量 origin、复杂代理逻辑、强连接复用诉求都叠在一起的场景,Pingora 的价值才会被充分放大。
参考资料
- How we built Pingora, the proxy that connects Cloudflare to the Internet
- Open sourcing Pingora: our Rust framework for building programmable network services
- Cloudflare changelog: Pingora now powers Cloudflare’s cache
- cloudflare/pingora GitHub README
- pingora-pool 0.8.0 docs.rs
- pingora-runtime docs.rs
- pingora-proxy
ProxyHttpdocs.rs - Pingora
HttpSession::reuse()docs.rs - Nginx upstream keepalive documentation
- Nginx upstream keepalive module source