結論先講
三個配置改動的效果加起來,比換框架大。 Redis cache 在讀多場景提升 6.5 倍(2,723 → 17,833 req/s),nginx keepalive +7%(零成本),PM2 多 worker +93%(4,888 → 9,461 req/s)。Django + 這三個優化的效能,可能比「裸的 Go」還好。
這一步怎麼來的:框架選了、DB 選了、Storage 也知道什麼時候加。但還有一層——nginx、multi-worker、水平擴展這些「不改程式碼只改配置」的東西,效果有多大?答案讓我們自己都嚇到。
為什麼測架構層
前面測了後端框架(Part 3)、DB(Part 5)、儲存(Part 6)。但真實系統不是「框架 + DB」這麼簡單——中間還有 nginx、Redis cache、multi-worker、水平擴展這些「架構層」的配置。
這兩篇 Infra 測試回答的問題是:不改程式碼,只改配置,能提升多少? 答案是:比換框架還多。
為什麼叫「免費午餐」
後端框架壓測(Part 3)告訴我們 Go 最快、Django 最慢。很多人的第一反應是「那我們換 Go 吧」。
但換框架的成本:
- 團隊學新語言(3-6 個月)
- 重寫所有 API(幾個月到一年)
- 新框架的 bug 和不熟悉(持續好幾年)
而下面三個優化:
- 改一行 nginx 配置
- 加幾行 Redis 程式碼
- 改一行 PM2 配置
就能得到接近換框架的效果。這就是免費午餐。
免費午餐 #1:Redis Cache-aside(+6.5 倍)
數據
| 場景 | 無 Cache | 有 Redis Cache | 提升 |
|---|---|---|---|
| 讀多(80% 讀 20% 寫) | 2,723 req/s | 17,833 req/s | 6.5 倍 |
| 標準 CRUD(50:50) | 2,614 req/s | 3,266 req/s | 25% |
讀多場景 6.5 倍,標準 CRUD 25%。你的應用讀寫比越高,Redis cache 的效果越大。
Cache-aside Pattern 怎麼運作
讀取:
1. 先查 Redis
2. Redis 有 → 直接回(快,幾 ms)
3. Redis 沒有 → 查 DB → 寫入 Redis → 回
寫入:
1. 寫 DB
2. 刪 Redis 中的對應 key(不是更新,是刪除)
3. 下次讀取會 cache miss → 自動從 DB 重新載入
為什麼寫入時「刪除」而不是「更新」Cache?因為如果同時有兩個寫入,「更新 cache」可能造成 race condition——後來的寫入先更新了 cache,但先來的寫入後更新 cache 覆蓋掉了。「刪除」就沒有這個問題。
注意事項
- Cache 失效策略: TTL(過期時間)要設合理。太短 → cache hit rate 低;太長 → 用戶看到舊資料
- Cache miss 懲罰: 第一次查詢(cache miss)比沒有 cache 時更慢——因為多了一次 Redis 的 roundtrip。但後續的 cache hit 遠遠補回來
- 和 bcrypt 的關係: Redis cache 對 auth 場景的影響 < 5%——因為 bcrypt 是 CPU-bound,cache 幫不了 CPU 的忙(呼應 第 25 篇)
免費午餐 #2:nginx keepalive(+7%,零成本)
數據
| 配置 | RPS |
|---|---|
| 無 nginx(直連 AP) | 基準 |
| nginx(無 keepalive) | -5%(proxy overhead) |
| nginx + keepalive | +7% |
為什麼加了中間層反而更快
沒有 keepalive 時,每個 request 都要建新的 TCP 連線:三次握手(SYN → SYN-ACK → ACK)。
開了 keepalive,nginx 和後端 AP 之間維持長連線——第一個 request 建立連線,後續的 request 直接複用同一條連線。省掉的不只是握手時間,還有 TCP slow start 的延遲。
怎麼開
upstream backend {
server app:3000;
keepalive 64; # 維持 64 條長連線
}
server {
location /api/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection ""; # 啟用 keepalive
}
}就這幾行,零成本 +7%。
免費午餐 #3:PM2 多 Worker(+93%)
數據
| Workers | RPS | 對比 |
|---|---|---|
| 1 | 4,888 | 基準 |
| 2 | 7,150 | +46% |
| 4 | 9,461 | +93% |
為什麼不是線性增長
1 worker → 4 workers,理論上應該快 4 倍(+300%),但實際只有 +93%。原因:
- CPU 核心數限制: Docker 只有 4 核,4 workers 已經跑滿。再加 worker 只會更慢(context switching)
- DB 連線池共享: 4 workers 搶同一個 pool,連線等待時間增加
- OS scheduling overhead: 多 process 之間的切換有成本
公式
Worker 數 = CPU 核數 × 1-2。4 核 CPU 開 4-8 workers。我們測的環境是 4 核開 4 workers,剛好是甜蜜點。
各語言的對應工具:
- Node.js: PM2 cluster mode
- Python: gunicorn —workers 4 / uvicorn —workers 4
- Java: 不需要(JVM 的 thread pool 天然支援多核)
- Go: 不需要(goroutine 天然用多核)
- PHP: PHP-FPM pm.max_children
三者加起來
| 優化 | 效果 | 成本 |
|---|---|---|
| Redis cache(讀多) | +550% | 加幾行程式碼 |
| nginx keepalive | +7% | 改一行配置 |
| Multi-worker | +93% | 改一行配置 |
如果三個都做(現實中它們是乘法關係,不是加法):
基準: 2,723 req/s(單 worker、無 cache、無 nginx)
+ multi-worker: 2,723 × 1.93 = 5,255 req/s
+ nginx keepalive: 5,255 × 1.07 = 5,623 req/s
+ Redis cache: 5,623 × 6.5 = 36,550 req/s(讀多場景)
從 2,723 到 36,550——13 倍提升,不改一行業務程式碼。
同時期 Go 在 CRUD 場景 的 Max RPS 是 74。即使考慮到場景不同,「Django + 三個免費午餐」的效能很可能超過「裸的 Go」。
這也是跨層洞察的第 4 條
回顧 Overview 頁面的跨層洞察:
「免費午餐」的效果超過換框架。Redis 快取在讀多場景提升 6.5 倍、nginx keepalive 提升 7%、多 worker 模式提升 93%。這三件事加起來比從 Django 換成 Go 的效果還大。
這條洞察就是本篇的核心。先把架構優化做完,再考慮換框架。 大部分團隊的效能問題不是因為選錯框架,而是因為沒做這三件事。
下一篇
水平擴展的天花板:2 台 +53%,4 台卡 DB — 加機器不是萬能的。壓測顯示 2 台確實提升 53%,但 4 台不會比 2 台更好——瓶頸從 application 轉移到了 DB 連線池。還有 K8s vs Docker Compose 的真實比較:1000 人以內 Docker Compose 就夠了。
本系列文章
完整 68 篇目錄見 系列首頁
← 上一篇:Redis 快 2.4 倍、ES 快 2.6 倍:每種儲存的甜蜜點 → 下一篇:水平擴展的天花板:2 台 +53%,4 台卡 DB