Cloudflare 在 2024 年初把 Pingora 開源出來的時候,他們公開的數字是這個 Rust 寫的代理框架在自家網路一天扛超過 1 兆次(trillion)HTTP 請求,至今累計處理量已經接近 1 千兆次(quadrillion)。換成同等流量,Pingora 比舊的 Nginx 服務少用大約 70% 的 CPU、67% 的記憶體,連線重用率從 87% 拉到 99.92%,相當於每天省下 434 年的 TLS handshake 時間。
這些數字看起來很驚人,但實際讀完官方 blog 與 GitHub README 之後,會發現 Pingora 跟 Nginx 不是同一個層級的東西。Nginx 是裝完就能用的 server,Pingora 是要寫 Rust 程式碼編譯出自己 binary 的 framework。把它們直接放在一起比「誰快」其實不太公平,但 Pingora 解決的問題確實是 Nginx 在超大規模場景下繞不過去的痛點。
這篇實作把 Pingora 從零裝起來,跑最小可用的反向代理、加上 round-robin 負載平衡、設定健康檢查,順便整理它跟 Nginx 在設計上到底差在哪裡,以及自架站在什麼情況下值得(或不值得)導入。
一、Pingora 的技術定位
1.1 不是 Nginx 替代品,是 framework
Cloudflare 自己在 README 裡寫得很直接:「Pingora is a library and toolset, not an executable binary... Pingora is the engine that powers a car, not the car itself.」用車子的比喻來說,Pingora 是引擎、不是整台車。
具體差異:
| 項目 | Nginx | Pingora |
|---|---|---|
| 形態 | 單一 binary,apt/yum 裝完就能跑 | Rust crate,要寫程式碼編譯 |
| 設定方式 | nginx.conf 設定檔 |
Rust 程式碼定義 trait 行為 |
| 學習門檻 | 改設定檔就能上手 | 要會 Rust + async + trait |
| 改邏輯 | 多半要 Lua(OpenResty)或寫 C module | 直接寫 Rust,編譯進 binary |
| 部署 | 套件管理直接裝 | 自己 build 出 binary 再部署 |
如果只是要把流量導到後端、做 TLS termination、加幾條 rewrite rule,Nginx 的設定檔幾分鐘就搞定;Pingora 同樣的事情你要先跑 cargo new、寫 Rust、再 cargo build --release。所以「比 Nginx 快」這個說法成立的前提,是你本來就需要用程式碼客製代理邏輯——例如根據 header 動態選後端、做 A/B 路由、串自家 control plane——這些用 Nginx 會寫到很掙扎的場景。
1.2 為什麼用 Rust 重寫
Cloudflare 並不是覺得 Nginx「不夠快」才換,他們在 How we built Pingora 裡列的真正痛點是:
1. Nginx 的 worker process 模型把連線池切碎
Nginx 是 multi-process,每個 worker 各自維護自己的 upstream 連線池。當請求落到 worker A,它只能重用 worker A 的連線——worker B 已經建好的連線完全用不到。Cloudflare 規模一大,水平擴 worker 反而讓連線重用率惡化,要對外建更多新連線才能撐住。
2. 一個請求綁一個 worker
Nginx 裡每個 request 從進來到結束都黏在同一個 worker 上。如果這個請求碰到吃 CPU 的工作或 blocking I/O,整顆 worker 卡住,core 之間負載不均。
3. C 寫的東西要再加功能太累
Nginx 是 C,要在上面長出複雜邏輯(特別是會碰記憶體的)成本與風險都高。Cloudflare 想要既快又能放心改的東西,就選了 Rust + 多執行緒共享 connection pool。
換成 Pingora 之後,所有 thread 共享同一個 upstream 連線池。對某個大型客戶,連線重用率從 87.1% 直接拉到 99.92%,新建連線數降為原本的 1/160——換算下來相當於每天節省 434 年的 TLS handshake 時間。
中位數的 time-to-first-byte 縮短 5ms,p95 縮短 80ms。CPU 與記憶體用量分別降 70% 與 67%。這些數字是 Cloudflare 真實上線後的數據,不是 lab benchmark。
二、架構與核心概念
2.1 Worker model

對比 Nginx 的多進程:每個 worker process 各有自己的 listener、自己的 connection pool,無法跨 worker 共享。

差別不只是「多執行緒比較潮」這麼簡單。多執行緒帶來:
- 連線池可以全 thread 共用:對 keep-alive 與 HTTP/2 multiplexing 是巨大優勢。
- Request 可以跨 thread 移動:CPU 重的任務可以丟給 worker thread,event loop 不被卡住。
- Graceful reload 比較好做:不像 Nginx 要 fork 出新 master 才能換 config。
2.2 與 Nginx 的設計差異
| 維度 | Nginx | Pingora |
|---|---|---|
| 並行模型 | multi-process + epoll | multi-thread async (Tokio) |
| 連線池 | per-worker | global / shared |
| 客製化 | C module 或 Lua(OpenResty) | 直接寫 Rust trait 實作 |
| TLS 後端 | OpenSSL | OpenSSL / BoringSSL / rustls / s2n-tls |
| HTTP/3 | 1.25+ 內建 QUIC | roadmap 中(截至 2026/04 還沒 GA) |
| 記憶體安全 | C,要靠 review | Rust 編譯期保證 |
Pingora 並沒有要把 Nginx 所有功能都長出來。例如 Nginx 用得很順手的 try_files、靜態檔伺服、FastCGI 這些跟反向代理本質沒關係的東西,Pingora 不打算碰——它就是專心做 reverse proxy。
三、實作範例
下面實作以 Debian 13、Rust 1.84+ 為前提。Pingora 從 2025 年起 MSRV(最低支援版本)跟著滾動,落後一兩個版本通常編不過。
3.1 最小可跑反向代理
先把工具鏈準備好:
# 裝 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustc --version
# 編譯 Pingora 需要的系統依賴(Debian/Ubuntu)
sudo apt-get install -y build-essential pkg-config libssl-dev cmake clang perl
建立專案:
cargo new my_pingora_proxy
cd my_pingora_proxy
編輯 Cargo.toml:
[package]
name = "my_pingora_proxy"
version = "0.1.0"
edition = "2021"
[dependencies]
pingora = { version = "0.4", features = ["proxy"] }
async-trait = "0.1"
tokio = { version = "1", features = ["full"] }
features = ["proxy"] 不能少,Pingora 把功能拆得很細,預設只給最小核心,proxy / cache / lb 都是可選 feature。
src/main.rs:
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 Self::CTX,
) -> Result<Box<HttpPeer>> {
// 第二個參數 false 表示不開 TLS;第三個是 SNI hostname
let peer = Box::new(HttpPeer::new(
"127.0.0.1:8080",
false,
"localhost".to_string(),
));
Ok(peer)
}
}
fn main() {
let mut server = Server::new(None).unwrap();
server.bootstrap();
let mut proxy = http_proxy_service(&server.configuration, MyProxy);
proxy.add_tcp("0.0.0.0:6199");
server.add_service(proxy);
println!("Pingora proxy listening on :6199");
server.run_forever();
}
整個反向代理只要這幾十行。ProxyHttp 這個 trait 是核心,upstream_peer() 決定請求要送到哪個後端。CTX 是這次請求的上下文,可以塞自己想跨 callback 帶的狀態。
編譯與啟動:
cargo build --release
./target/release/my_pingora_proxy
第一次編譯會抓很多 crate,慢一點是正常的(5~10 分鐘)。
開另一個 terminal 用 Python 起一個假的後端:
mkdir /tmp/web && cd /tmp/web
echo "hello from backend" > index.html
python3 -m http.server 8080
測試:
curl -i http://127.0.0.1:6199/
# HTTP/1.1 200 OK
# hello from backend
通了。這就是 Pingora 的最小代理,比起 Nginx 一行 proxy_pass http://127.0.0.1:8080; 是繁瑣很多,但這個架構的價值在後面要加客製邏輯時才會展現。
3.2 Load balancing
實際生產環境後端不會只有一台。Pingora 的 pingora-load-balancing 提供現成的 round-robin、random、consistent hashing 演算法。
Cargo.toml 加上:
pingora-load-balancing = "0.4"
改寫 main.rs:
use async_trait::async_trait;
use pingora::prelude::*;
use pingora_load_balancing::{selection::RoundRobin, LoadBalancer};
use std::sync::Arc;
pub struct LB(Arc<LoadBalancer<RoundRobin>>);
#[async_trait]
impl ProxyHttp for LB {
type CTX = ();
fn new_ctx(&self) -> Self::CTX {}
async fn upstream_peer(
&self,
_session: &mut Session,
_ctx: &mut Self::CTX,
) -> Result<Box<HttpPeer>> {
let upstream = self
.0
.select(b"", 256)
.ok_or_else(|| Error::explain(ErrorType::InternalError, "no upstream"))?;
println!("forwarding to {:?}", upstream);
Ok(Box::new(HttpPeer::new(upstream, false, "".to_string())))
}
}
fn main() {
let mut server = Server::new(None).unwrap();
server.bootstrap();
let upstreams =
LoadBalancer::try_from_iter(["127.0.0.1:8080", "127.0.0.1:8081", "127.0.0.1:8082"])
.unwrap();
let lb = Arc::new(upstreams);
let mut proxy = http_proxy_service(&server.configuration, LB(lb));
proxy.add_tcp("0.0.0.0:6199");
server.add_service(proxy);
server.run_forever();
}
select(b"", 256) 第一個參數是給 consistent hashing 用的 key,round-robin 模式下會被忽略;第二個是 retry 次數上限(避開那些已經被標記 unhealthy 的 backend)。
開三個後端 server 後反覆 curl,可以看到 stdout 印出輪流送往不同 upstream。
3.3 Health check 與失敗 retry
光輪詢還不夠,後端掛了要能感知到。Pingora 內建 TCP 與 HTTP health check,但要把它變成 background task 跑:
use pingora::services::background::background_service;
use pingora_load_balancing::health_check::TcpHealthCheck;
use std::time::Duration;
fn main() {
let mut server = Server::new(None).unwrap();
server.bootstrap();
let mut upstreams = LoadBalancer::try_from_iter([
"127.0.0.1:8080",
"127.0.0.1:8081",
"127.0.0.1:8082",
])
.unwrap();
// 設定 health check
let hc = TcpHealthCheck::new();
upstreams.set_health_check(hc);
upstreams.health_check_frequency = Some(Duration::from_secs(5));
// 把 LB 包成 background service,定期跑 health check
let bg = background_service("health check", upstreams);
let lb = bg.task();
let mut proxy = http_proxy_service(&server.configuration, LB(lb));
proxy.add_tcp("0.0.0.0:6199");
server.add_service(bg);
server.add_service(proxy);
server.run_forever();
}
幾個重點:
background_service()會把 LB 變成獨立的 background task,每 5 秒打 TCP 連線檢查所有 backend。bg.task()拿到的是同一個 LB instance 的 Arc 參考,proxy 跟 health checker 共用狀態。- 故意把 8081 後端關掉,幾秒後
select()就不會再選到它;重啟後會自動恢復。
如果要更細緻的 HTTP health check(檢查特定 path、status code),把 TcpHealthCheck::new() 換成 HttpHealthCheck 並設定 request path。
至於 upstream 連線失敗的 retry,Pingora 用 fail_to_connect() 與 fail_to_proxy() 這兩個 callback,可以在程式內判斷錯誤類型決定要不要重試、換 backend。比起 Nginx proxy_next_upstream 的設定旗標,這邊邏輯能寫得很細。
四、效能對比 vs. Nginx
4.1 Cloudflare 公開的 benchmark
Cloudflare 自己給的數字是生產環境實測,不是 synthetic benchmark:
| 指標 | 改善幅度 |
|---|---|
| CPU 使用 | -70% |
| 記憶體使用 | -67% |
| 中位數 TTFB | -5ms |
| p95 TTFB | -80ms |
| 連線重用率(某大型客戶) | 87.1% → 99.92% |
| 新連線數 | 約原本的 1/160 |
要注意這個對比的底是「Cloudflare 自己改過、跑了多年的 Nginx」,不是你 apt install 來的 vanilla Nginx。Cloudflare 在 Nginx 上累積的客製碼非常重,他們是把客製過的 Nginx 換成 Pingora,省下來的 CPU/Memory 來自架構層面的勝利(多執行緒、共享連線池),不是 Rust 比 C 快。
社群獨立做的 benchmark 結果不一致:GitHub issue #372 裡有人實測單純 throughput Nginx 還略勝;Pingora 在低延遲與穩定性上略好,但都在誤差範圍。也就是說:單純比每秒多打幾個 HTTP request,Pingora 不會有戲劇性贏過 Nginx。它真正的優勢在連線池共享、長時間穩定運行下的資源節省。
4.2 適合場景與不適合場景
適合:
- 大量 keep-alive、HTTP/2 multiplexing 的反向代理場景
- 需要動態決定 upstream 的場景(A/B、灰度、tenant routing、地理路由)
- 想用 Rust 寫整條控制平面、需要把 proxy 邏輯與業務邏輯整合
- 要 BoringSSL 或 post-quantum crypto
不適合:
- 單純的 web server(serve 靜態檔、跑 PHP-FPM)——直接 Nginx 或 Caddy
- 團隊沒人會 Rust,而且也沒打算學
- 要的是設定檔即生效、不想 build binary 的便利
- 需要 HTTP/3 / QUIC(截至 2026/04 還沒 GA)
順帶一提,如果想要「Pingora 的引擎 + Nginx-like 設定檔」的折衷方案,社群有 Pingap;Internet Security Research Group(Let's Encrypt 那個 ISRG)也基於 Pingora 做了 River,目標就是「memory-safe 的 Nginx 替代」。
五、與 Nginx / HAProxy / Traefik 比較
| 工具 | 語言 | 設計目標 | 上手難度 | 設定方式 | 生態成熟度 | 適合單機自架 |
|---|---|---|---|---|---|---|
| Nginx | C | 多用途 web server + proxy | 低 | nginx.conf | 極成熟 | ✓ |
| HAProxy | C | L4/L7 高效能 LB | 中 | haproxy.cfg | 成熟 | ✓ |
| Traefik | Go | 雲原生、自動服務發現 | 中低 | YAML / labels | 成熟 | ✓(搭 Docker/K8s 更佳) |
| Envoy | C++ | service mesh data plane | 高 | YAML / xDS API | 成熟 | △ 太重 |
| Caddy | Go | 自動 HTTPS、容易上手 | 極低 | Caddyfile | 中 | ✓ |
| Pingora | Rust | 客製化代理 framework | 高 | Rust 程式碼 | 早期 | △ 殺雞用牛刀 |
選擇邏輯大概是:
- 一般網站、PHP/Node 後端 → Nginx 或 Caddy
- 純 L7 LB、要做複雜 routing → HAProxy
- K8s ingress → Traefik / Nginx Ingress / Envoy
- service mesh → Envoy / Linkerd
- 寫得起 Rust、要極端高並發 + 客製邏輯 → Pingora
六、實際採用建議
幾個現實的問題我覺得要先想清楚:
1. 團隊有沒有人能 maintain Rust 程式碼?
Pingora 不是裝完就忘的東西,它是你產品的一部分原始碼。沒人會 Rust 的話,下次要加 feature、debug、升級 dependency,都會卡住。
2. 真的需要客製代理邏輯嗎?
如果只是 proxy_pass、改幾個 header、做 TLS termination,老老實實用 Nginx。Pingora 的價值在「Nginx 配置已經寫到很噁心、你想要直接寫程式」的那一刻才會出現。
3. 對 HTTP/3 的需求
Nginx 從 1.25 開始有 QUIC、Caddy 也支援,Pingora 還沒 GA。如果要前線跑 HTTP/3 給瀏覽器,Pingora 還不到位。
4. 流量規模值不值得
70% CPU 節省聽起來很爽,但你得是「每天打幾億請求、機房裡有幾百台 LB」的規模才有感。如果你每秒幾百到幾千 RPS,Nginx 的 CPU 使用率本來就在個位數,省 70% 變成「省 1 個 core」,還抵不過引入 Rust 帶來的維運成本。
老實講,自架站想試 Pingora 比較合理的玩法是當作 Rust 網路程式設計的學習素材,而不是真的拿來換 Nginx。除非你的代理邏輯複雜到 Lua/OpenResty 已經寫不下去。
七、踩雷與限制
實際操作過程中遇到的幾個點:
- Rust 版本要新:Pingora 的 MSRV 是滾動 6 個月推進,距離開源時的 1.72 已經拉到 1.84+。Debian/Ubuntu 套件版本太舊,乖乖用
rustup裝。 - 編譯依賴:
cmake、clang、perl、pkg-config、libssl-dev這幾個沒裝會在 build BoringSSL /libz-ng-sys那邊噴錯。錯誤訊息不直觀,第一次踩會花時間。 - Crate feature 要選對:
pingoracrate 是 façade,proxy、cache、lb都是可選 feature,沒打開會編不到對應模組。 - API 還會變:Cloudflare 自己在 README 寫
API stability is not guaranteed,從 0.1 升到 0.4 中間就有幾個破壞性變更(例如LoadBalancer的構造方式)。生產環境記得釘版本。 - 沒有 hot reload:Nginx
nginx -s reload一秒搞定,Pingora 的 graceful upgrade 要靠 SIGQUIT + 啟動新 binary,操作流程比較繁瑣。 - macOS / Windows 不是首選平台:開發機用 macOS 通常沒問題(會有 feature 限制),生產跑 Linux x86_64 / aarch64 才是主軸;Windows 是社群 best-effort。
- 記憶體安全 ≠ 邏輯安全:Rust 編譯期防住的是 memory unsafety,業務邏輯漏洞、SSRF、header injection 該檢查還是要檢查。
八、小結
Pingora 是一個框架,定位很清楚:給 Cloudflare 等級的玩家寫客製 proxy 用。它解決的問題(多執行緒共享連線池、Rust 記憶體安全)對超大規模業務是真痛點,70% CPU、67% 記憶體節省的數字也是真的。
但對絕大多數自架站來說,這些優勢沒有作用點。Nginx 跑得好好的,沒必要為了「比 Nginx 快」這幾個字,把一個原本五行設定檔的東西改寫成幾百行 Rust 程式碼。比較合理的態度是:把 Pingora 當成「值得追蹤的反向代理新典範」,等它的高階包裝(Pingap、River)成熟,或等到自己的代理邏輯複雜到非得寫程式不可的那一天,再認真考慮導入。
Cloudflare 把這套東西開源出來這件事本身就值得鼓掌——畢竟整個 Internet 中介層被一坨 1990 年代的 C code 撐著,已經是事實。Rust 把這層慢慢替換掉,是好事。
參考資料
- 從零搭建比 Nginx 還快的反向代理 - 運維躬行錄(微信公眾號原文)
- Open sourcing Pingora - Cloudflare Blog
- How we built Pingora - Cloudflare Blog
- How Pingora keeps count - Cloudflare Blog
- cloudflare/pingora - GitHub
- Pingora is Not an Nginx Replacement - Navendu Pottekkat
- Announcing River - ISRG / Prossimo
- Pingap - Nginx-like reverse proxy on Pingora

發佈留言