結論先講
壓測平台的核心價值不是「跑壓測」,而是「讓壓測結果可被重複、可被比較、可被自動分析」。 用 CLI 跑一次 k6 很容易,但要比較 9 個框架 × 7 種 VU × 3 種場景的數據,你需要一個平台。
為什麼不直接用 k6 CLI
k6 本身是一個很好的壓測工具。你可以寫一個腳本、命令列跑一次、看 stdout 的摘要。對於「測一下這個 API 撐不撐得住」的場景,這就夠了。
但當需求變成:
- 比較 9 個後端框架在同一場景下的表現
- 每個框架跑 7 種 VU(10, 50, 100, 500, 1K, 5K, 10K)
- 每次跑完自動判斷瓶頸在哪裡
- 結果要能回溯、能比較、能產報告
光靠 k6 CLI 就不夠了。你需要:
- 腳本管理: 版本化、可編輯、可重複使用
- 指標儲存: 即時寫入時序資料庫,不是跑完看一次就丟掉
- 視覺化: 不是看數字,是看趨勢
- 自動分析: 20 條規則掃過去,不是人眼盯著數字猜
整體架構
┌─────────────────────────────────────────────────┐
│ Web UI (Next.js 15) │
│ port 13000 │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Scenario │ │ Monaco │ │ Results Viewer │ │
│ │ Manager │ │ Editor │ │ + Comparison │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
└─────────────────┬───────────────────────────────┘
│ REST API + WebSocket
┌─────────────────▼───────────────────────────────┐
│ API Server (Express + TS) │
│ port 14000 │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Scenario │ │ k6 │ │ Rule Engine │ │
│ │ CRUD │ │ Runner │ │ (20 rules) │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
└───┬──────────────┬──────────────┬───────────────┘
│ │ │
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌──────────────┐
│ PG 16 │ │ InfluxDB │ │ Grafana 11 │
│ Config │ │ Metrics │ │ Dashboard │
│ Reports│ │ (k6→xk6) │ │ port 13001 │
└────────┘ └──────────┘ └──────────────┘
│
▼
┌──────────┐
│ Target │
│ API │
│ (被測系統) │
└──────────┘
每一層都有明確的職責,接下來逐層說明。
層次一:壓測引擎(k6 + xk6-influxdb)
為什麼選 k6
壓測工具的選擇不少:JMeter、Gatling、Locust、k6、Artillery。選 k6 的理由:
| 工具 | 語言 | 資源消耗 | 腳本可讀性 | 即時輸出 |
|---|---|---|---|---|
| JMeter | XML + GUI | 高(JVM) | 差 | 需外掛 |
| Gatling | Scala DSL | 中(JVM) | 中 | 有 |
| Locust | Python | 中 | 好 | 有 |
| k6 | JavaScript | 低(Go runtime) | 好 | 有 |
| Artillery | YAML + JS | 中(Node) | 好 | 有 |
k6 用 Go 寫的 runtime 執行 JavaScript 腳本,資源消耗遠低於 JVM 系列。腳本就是 JS,前後端工程師都看得懂。而且它的 stages 設計讓 VU 的逐步增加非常直覺:
export const options = {
stages: [
{ duration: '1m', target: 50 }, // 1 分鐘內爬到 50 VU
{ duration: '3m', target: 50 }, // 維持 50 VU 跑 3 分鐘
{ duration: '1m', target: 100 }, // 1 分鐘內爬到 100 VU
{ duration: '3m', target: 100 }, // 維持 100 VU 跑 3 分鐘
{ duration: '1m', target: 0 }, // 收尾
],
};xk6-influxdb:即時指標輸出
k6 預設把結果輸出到 stdout。用 xk6-influxdb 擴展,讓 k6 在壓測進行中即時把每個請求的指標寫進 InfluxDB:
http_req_duration: 每個請求的 response timehttp_req_failed: 是否失敗http_reqs: 請求計數(算 RPS 用)vus: 當前虛擬用戶數
這些指標按秒寫入 InfluxDB,Grafana 可以即時畫圖。壓測還在跑,你就能看到系統正在崩潰。
層次二:指標儲存(InfluxDB v2)
為什麼用時序資料庫
壓測數據本質上是時序數據:每秒的 RPS、每秒的 P95 latency、每秒的錯誤率。用 PostgreSQL 也能存,但查詢效率差很多。
InfluxDB 的優勢:
- 時間視窗聚合: 「過去 5 分鐘的 P95 response time」一句 Flux query 搞定
- 自動降採樣: 保留最近 7 天的秒級數據,更早的自動聚合成分鐘級
- Grafana 原生整合: 不需要寫 adapter
資料保留策略
秒級數據 → 保留 7 天
分鐘級數據 → 保留 30 天
報告摘要 → 永久保留(存 PostgreSQL)
不需要永久保留每秒的數據。分析完的結論存 PostgreSQL,原始數據可以清掉。
層次三:視覺化(Grafana 11)
為什麼不自己畫圖
因為 Grafana 已經把壓測視覺化做到很好了。自己用 Recharts 或 D3 畫,要花很多時間處理:
- 時間軸對齊
- 多指標疊圖
- 動態縮放
- 資料來源切換
Grafana 一個 dashboard 就搞定。我們的 dashboard 包含:
- VU 與 RPS 趨勢: 左軸 VU、右軸 RPS,看吞吐量跟不跟得上用戶數
- Response Time 分位數: P50、P90、P95、P99 四條線,看 tail latency
- Error Rate: 錯誤率趨勢,和 VU 疊在一起看「什麼時候開始壞」
- HTTP Status 分佈: 200/400/500 的比例,判斷是 client error 還是 server error
但 Web UI 還是需要自己做
Grafana 負責「看即時數據」。但壓測平台還需要:
- 場景管理(建立、編輯、排程)
- 腳本編輯(Monaco Editor)
- 歷史結果比較(跨框架、跨 VU)
- 規則引擎分析結果
這些是 Next.js Web UI 的職責。
層次四:規則引擎
為什麼要自動分析
一次壓測跑完,你會得到:
- 每秒的 RPS
- 每個請求的 response time(P50/P90/P95/P99)
- 錯誤率
- 各 HTTP status code 的數量
要判斷「系統在哪裡開始出問題」「瓶頸是什麼」,需要把這些數字交叉比對。人可以做,但當你有 9 個框架 × 7 種 VU = 63 份報告要分析,人工分析不現實。
20 條分析規則
規則引擎目前有 20 條規則,分成幾大類:
Response Time 規則
| 規則 | 觸發條件 | 判定 |
|---|---|---|
| P95 過高 | P95 > 2000ms | 警告 |
| P99 離群 | P99 / P50 > 10 | Tail latency 問題 |
| Response time 急劇上升 | 斜率 > 閾值 | 系統正在崩潰 |
Throughput 規則
| 規則 | 觸發條件 | 判定 |
|---|---|---|
| RPS 飽和 | VU 加倍但 RPS 不變 | 吞吐量瓶頸 |
| RPS 下降 | RPS 比前一階段低 | 系統過載 |
Error 規則
| 規則 | 觸發條件 | 判定 |
|---|---|---|
| 錯誤率閾值 | error rate > 1% | 穩定性問題 |
| 錯誤率飆升 | error rate 斜率突增 | 系統正在崩潰 |
| 5xx 比例 | 5xx / total error > 50% | Server-side 問題 |
瓶頸歸因規則
| 規則 | 判斷依據 | 歸因 |
|---|---|---|
| CPU 阻塞 | 高 P95 + 低 RPS + 無 DB 等待 | CPU-bound(如 bcrypt) |
| DB 連線池 | 錯誤訊息含 pool/timeout | 連線池不足 |
| 記憶體壓力 | response time 隨時間線性上升 | Memory leak |
規則引擎的設計原則
interface Rule {
id: string;
name: string;
category: 'response_time' | 'throughput' | 'error' | 'bottleneck';
evaluate: (metrics: BenchmarkMetrics) => RuleResult;
severity: 'info' | 'warning' | 'critical';
}
interface RuleResult {
triggered: boolean;
message: string;
evidence: Record<string, number>; // 佐證數據
suggestion: string; // 修復建議
}每條規則獨立運作,吃同一份 metrics 資料,產出結構化的結果。規則之間不互相依賴——這讓規則的新增和修改很安全。
層次五:資料管理(PostgreSQL 16)
InfluxDB 存即時指標,PostgreSQL 存其他所有東西:
- Scenario 設定: 測試場景的名稱、VU 設定、目標 API、k6 腳本
- Run 記錄: 每次跑的時間、狀態、結果摘要
- Rule 分析結果: 每次跑完規則引擎的分析
- Framework 設定: 各框架的 port、health check URL、Docker config
用 Sequelize ORM,schema migration 管理資料庫版本。
為什麼全部 Docker Compose 化
整個平台用 Docker Compose 管理:
services:
web: # Next.js Web UI
api: # Express API Server
postgres: # 設定/報告儲存
influxdb: # k6 指標
grafana: # 視覺化
# --- 被測系統 ---
express-ts: # Framework 1
django: # Framework 2
fastapi: # Framework 3
# ... 其他 6 個框架
mysql: # 被測系統的資料庫
redis: # 被測系統的快取Docker Compose 化的好處:
- 環境一致: 不管在 Mac 還是 Linux 跑,環境一模一樣
- 資源可控: 可以限制每個框架的 CPU 和 RAM(壓測公平性的關鍵,下一篇會詳細講)
- 一鍵啟動:
docker compose up -d就能開始測 - 互相隔離: 框架之間不會搶資源
資料流:從點擊「Run」到看報告
- 用戶在 Web UI 選場景、選框架、點 Run
- Web UI 呼叫 API Server 的
/runsendpoint - API Server 產生 k6 腳本(注入 VU、duration、target URL)
- API Server spawn k6 process,k6 開始打被測系統
- k6 即時把指標寫入 InfluxDB(透過 xk6-influxdb)
- API Server 透過 WebSocket 把進度推給 Web UI
- Grafana 同時從 InfluxDB 拉數據,即時更新 dashboard
- k6 跑完,API Server 從 InfluxDB 拉最終數據
- 規則引擎跑 20 條規則,產出分析報告
- 結果存 PostgreSQL,Web UI 顯示報告
步驟 9 是關鍵。人不需要盯著 Grafana 看「系統什麼時候開始壞」——規則引擎會告訴你。
這個架構的取捨
選了什麼
- InfluxDB v2 而非 Prometheus: k6 原生支援 InfluxDB output,不需要額外的 exporter
- PostgreSQL 而非 SQLite: 多人協作時需要真正的資料庫
- spawn process 而非 k6 library mode: process 隔離更乾淨,k6 掛了不會拖垮 API Server
- 自建規則引擎而非用 Grafana Alert: 規則需要跨指標交叉比對,Grafana Alert 做不到
沒選什麼
- 沒有分散式壓測: 單機就能打到框架極限,不需要 k6 cloud 或多機部署
- 沒有 CI/CD 整合: 目前是研究用途,不是跑在 pipeline 裡
- 沒有用戶認證: 內部工具,不需要
下一篇
控制變因的藝術 — 當你要比較 9 個後端框架的效能,「公平」比「快」更重要。怎麼用 Docker 資源限制、統一 DB 配置、統一 hash 算法來確保比較基準一致。
本系列文章
完整 68 篇目錄見 系列首頁
← 上一篇:為什麼需要 API 壓力測試?從「能動」到「能撐」 → 下一篇:控制變因的藝術:跨框架壓測怎麼做到公平