1. Cloudflare 的代理架构演进史
1.1 第一代:Nginx 时代(2009-2019)
Cloudflare 成立于 2009 年,最初的边缘代理架构基于 Nginx。这是一个合理的选择:
-
成熟稳定:Nginx 经过千万级网站验证
-
事件驱动:epoll/kqueue 支撑高并发
-
模块化:C 模块扩展功能
┌─────────────────────────────────────────┐
│ Cloudflare Edge (2009) │
│ ┌─────────┐ ┌─────────┐ ┌─────┐ │
│ │ DNS │───→│ Nginx │───→Cache│ │
│ └─────────┘ └────┬────┘ └─────┘ │
│ │ │
│ ┌────┴────┐ │
│ │ Upstream │ │
│ │ (Origin) │ │
│ └─────────┘ │
└─────────────────────────────────────────┘
1.2 Nginx 的瓶颈显现
随着 Cloudflare 规模指数级增长,Nginx 架构的固有局限开始暴露:
| 维度 | Nginx 的限制 | Cloudflare 的需求 |
|---|---|---|
| 连接管理 | 每个连接一个文件描述符,工作进程隔离 | 千万级长连接共享 |
| 内存模型 | 多进程 + 共享内存(有限) | 统一内存池,精细控制 |
| 热更新 | reload 信号,连接可能中断 | 零停机,连接保持 |
| 可观测性 | 有限的状态导出 | 实时精细化指标 |
| 定制能力 | C 模块开发成本高 | Rust 安全扩展 |
c
// Nginx 的 Worker 进程模型 ------ 内存隔离是双刃剑
// 每个 worker 独立处理连接,无法跨进程复用连接池
nginx.conf:
worker_processes auto; # 通常 CPU 核数
events {
worker_connections 4096; # 每个 worker 的连接上限
}
1.3 第二代:内部框架(2019-2022)
Cloudflare 开始构建内部代理框架,基于 Rust + Tokio:
┌─────────────────────────────────────────────────────┐
│ Cloudflare Internal Proxy (2019) │
│ ┌─────────────┐ ┌───────────────────────────┐ │
│ │ Tokio │ │ Unified Runtime │ │
│ │ Runtime │───→│ (Single-process, async) │ │
│ └─────────────┘ └───────────────────────────┘ │
│ │
│ 关键改进: │
│ - 单进程异步模型,消除 worker 隔离 │
│ - 连接池全局共享 │
│ - 零拷贝转发优化 │
└─────────────────────────────────────────────────────┘
这个内部框架在 Cloudflare 生产环境验证了 3 年,处理了数万亿请求。
1.4 第三代:Pingora 开源(2022-至今)
2022 年,Cloudflare 将内部框架开源为 Pingora:
rust
// Pingora 的架构哲学:Library > Framework
// 不是给你配置文件的代理,而是给你构建代理的积木
use pingora_core::server::Server;
use pingora_core::upstreams::peer::HttpPeer;
use pingora_proxy::{ProxyHttp, Session};
#[async_trait]
impl ProxyHttp for MyProxy {
async fn upstream_peer(&self, session: &mut Session) -> Result<Box<HttpPeer>> {
// 完全自定义 upstream 选择逻辑
let peer = self.load_balancer.select(session).await?;
Ok(Box::new(HttpPeer::new(peer)))
}
}
2. Pingora 相比 Nginx 的核心优势
2.1 架构层面的本质差异
┌─────────────────────────────────────────────────────────────────┐
│ 架构对比:进程模型 │
├───────────────────────────┬─────────────────────────────────────┤
│ Nginx │ Pingora │
│ ┌─────┐ ┌─────┐ ┌─────┐ │ ┌─────────────────────────────────┐│
│ │ W1 │ │ W2 │ │ W3 │ │ │ Single Process ││
│ │[EP] │ │[EP] │ │[EP] │ │ │ ┌─────────┐ ┌─────────┐ ││
│ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ Thread1 │ │ Thread2 │ ... ││
│ │ │ │ │ │ │[Tokio] │ │[Tokio] │ ││
│ ┌──┴───────┴───────┴──┐ │ │ └────┬────┘ └────┬────┘ ││
│ │ Shared Memory │ │ │ └─────────────┘ ││
│ │ (Limited usage) │ │ │ │ ││
│ └─────────────────────┘ │ │ ┌────┴────┐ ││
│ │ │ │ Shared │ ││
│ EP = Event Poll (epoll) │ │ │ State │ ││
│ │ │ └─────────┘ ││
└──────────────────────────┴─────────────────────────────────────┘
关键差异:
- Nginx:多进程模型,worker 间连接池无法共享
- Pingora:单进程多线程,Tokio runtime 统一调度,连接池全局可见
2.2 连接复用的革命性提升
Nginx 的多进程模型带来了一个根本性限制:每个 worker 进程只能看到自己建立的上游连接 。如果 worker A 与 backend-1:8080 有 10 个空闲连接,worker B 的新请求也路由到同一 backend,它无法复用 worker A 的连接,只能重新建立。
Pingora 的单进程多线程模型消除了这个边界:所有 async 任务共享同一个 ConnectionPool(基于 DashMap 实现无锁并发访问)。任意线程都能取到任意线程归还的连接,全局复用率自然远高于 Nginx。
连接池的 key 设计也体现了精细化:不仅按 addr:port 区分,还区分 TLS/plaintext、SNI、ALPN 协议、出口绑定 IP------这保证了不同安全上下文的连接不会被错误复用。
注:以下性能数据来自 Cloudflare 2022 年发布的博客文章,基于其生产环境的测量,具体数值可能因工作负载不同而有较大差异。
| 指标 | Nginx | Pingora | 提升 |
|---|---|---|---|
| 长连接复用率 | ~60% | ~95% | 1.6x |
| 内存占用/连接 | ~2.5KB | ~0.5KB | 5x |
| CPU 使用率(同负载) | 100% | ~60% | 1.7x |
2.3 内存效率的代际差距
Pingora 内存效率优势来自两个层面:
连接级 :Nginx 为每个连接分配固定大小的内存池(通常 2--4 KB),无论连接是否活跃。Pingora 的连接以 PooledConnection 表示,空闲时只保留元数据(几百字节),I/O 缓冲区(BytesMut)在处理请求时按需分配,请求完成后可以释放或缩小。
转发级 :Pingora 在转发 HTTP 请求头时使用 write_vectored(writev 系统调用),把多个 Bytes 切片组成 iovec 数组一次性发出。这些切片都是对原始解析缓冲区的视图引用,不需要先拼接到新缓冲区------从读取到转发,头部数据没有发生过一次 memcpy。
2.4 热升级:fd 继承机制
Nginx 的 reload 信号实际上是优雅重启:启动新 worker 进程,旧 worker 处理完当前请求后退出。这期间:
- 新 worker 开始接受新连接
- 旧 worker 上的 keep-alive 连接被强制关闭(客户端会看到 connection reset)
- 上游连接池完全重建
Pingora 的热升级基于 Unix 文件描述符继承 (SCM_RIGHTS):
旧进程 ──fork + exec──→ 新进程
│ │
│ 传递所有 listen fd │
└──────SCM_RIGHTS──────→ 新进程开始 accept()
│
│ 等待现有请求完成(最多 N 秒)
│
└──── 退出
listen socket 的文件描述符被传递给新进程,现有 TCP 连接保持不断(fd 引用计数不归零),客户端完全无感知。上游连接池需要重建,但这对客户端没有影响。
3. 什么时候应该选择 Pingora?
3.1 Pingora 适合的场景
| 场景 | 原因 |
|---|---|
| 超大规模边缘代理 | 千万级连接,连接复用至关重要 |
| 需要深度定制 | Rust 代码扩展,而非 C 模块 |
| 多协议网关 | HTTP/1, HTTP/2, gRPC, WebSocket 统一处理 |
| 与 Rust 生态集成 | 直接复用 Rust crate 生态 |
| 极致性能要求 | 零拷贝、精细内存控制 |
3.2 Nginx 仍然优秀的场景
| 场景 | 原因 |
|---|---|
| 简单静态文件服务 | Nginx 成熟,配置简单 |
| 已有成熟运维体系 | 配置文件管理、监控生态丰富 |
| 团队无 Rust 经验 | 学习成本需要考虑 |
| 需要特定第三方模块 | OpenResty/Lua 生态依赖 |
4. Cloudflare 的迁移经验
4.1 迁移时间线
2019: 内部原型开发,验证 Rust + Tokio 可行性
2020: 小规模生产测试,处理 1% 流量
2021: 大规模部署,处理 50% 流量
2022: 完全替代 Nginx,开源 Pingora
2023: Pingora 处理 Cloudflare 100% 边缘流量
4.2 关键教训
- 不要直接平移配置:Pingora 是 library,需要重新思考架构
- 充分利用类型系统:Rust 的编译期检查在生产环境中防止了大量 bug
- 连接管理是核心:投资源优化连接池,回报巨大
- 可观测性先行:完善的 metrics 和 tracing 是运维基础
5. 总结:代理架构的三代演进
| 代际 | 代表 | 核心模型 | 关键特征 |
|---|---|---|---|
| 第一代 | Nginx/Apache | 多进程/多线程 | 稳定、成熟、进程隔离 |
| 第二代 | Envoy | 事件驱动 + xDS | 云原生、服务网格 |
| 第三代 | Pingora | 异步 Rust | 安全、零拷贝、极致性能 |
Pingora 不是简单地"更快",而是代表了系统编程语言演进 (C → Rust)和异步运行时成熟(epoll → io_uring/Tokio)在代理场景的结合。
Cloudflare 的选择表明:当规模达到一定程度,基础设施值得用现代系统语言重写。