autocannon API 端點壓力測試套件介紹
autocannon 是一款受 wrk 啟發、由 Node.js 撰寫的高效能 HTTP/1.1 基準測試(Benchmarking)工具。其支援 HTTP Pipelining ⚡,在 Node.js 環境中能榨出極高吞吐量,非常適合用於測試 REST API、GraphQL 或微服務的極限乘載力。
一、 📦 安裝與前置準備
你可以透過 pnpm 選擇「全域安裝」或「作為專案開發依賴 (Dev Dependency)」:
1. 🌐 全域安裝 (常用於個人電腦快速測試)
pnpm add -g autocannon
2. 📁 專案開發依賴 (推薦,便於團隊與 CI/CD 共享版本)
pnpm add -D autocannon
3. ⚡ 免安裝直接執行 (使用 pnpm dlx)
如果你不想預先安裝,可以直接呼叫:
pnpm dlx autocannon http://localhost:3000
二、 🛠️ CLI 快速上手與常用參數
CLI 是最快速驗證 API 效能的方式。
1. ⚙️ 常用選項說明
| 參數 | 長名稱 | 預設值 | 說明 |
|---|---|---|---|
-c |
--connections |
10 |
並發連線數 (Concurrent Connections) 👥 |
-d |
--duration |
10 |
壓測持續時間 (秒) ⏱️ |
-p |
--pipelining |
1 |
每個連線同時送出的管線化請求數 (極限測試時建議調整為 10 或 20) 🚀 |
-a |
--amount |
無 | 總請求次數 (若設定此項,將忽視 -d 時間設定) 🎯 |
-m |
--method |
GET |
HTTP 請求方法 (GET, POST, PUT, DELETE…) 📮 |
-H |
--headers |
無 | 自訂 Header,可重複傳入多個 🔑 |
-b |
--body |
無 | 請求主體 (Payload) 📦 |
-w |
--workers |
1 |
開啟多執行緒 (Worker Threads) 發送請求,避免壓測端 CPU 成為瓶頸 🧠 |
-t |
--timeout |
10 |
單一請求超時時間 (秒) ⏳ |
-l |
--latency |
false |
輸出詳細的延遲統計表 (包含 p99.9 等百分位數) 📊 |
--json |
--json |
false |
以 JSON 格式輸出結果 (適合系統整合) 📄 |
2. 💡 實用 CLI 範例
- 🔹 基礎 GET 壓測 (50 連線,持續 15 秒)
pnpm dlx autocannon -c 50 -d 15 http://localhost:3000/api/health
- 🔹 POST JSON Payload 測試
pnpm dlx autocannon -c 100 -d 10 \
-m POST \
-H "Content-Type=application/json" \
-H "Authorization=Bearer YOUR_ACCESS_TOKEN" \
-b '{"userId": 101, "action": "checkout"}' \
http://localhost:3000/api/orders
- 🔹 極限效能測試 (開啟 Pipelining 與 Worker Threads)
pnpm dlx autocannon -c 200 -d 10 -p 10 -w 4 http://localhost:3000/api/v1/products
三、 📊 測試結果與指標詳細判讀
執行後,autocannon 會輸出如下的表格與統計:
Running 10s test @ http://localhost:3000/api/v1/products
100 connections
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────┤
│ Latency │ 1 ms │ 3 ms │ 18 ms │ 45 ms│ 4.12 ms │ 8.54 ms │ 98 ms│
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────┘
┌───────────┬────────┬────────┬────────┬────────┬───────────┬────────┬────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────────┼────────┼────────┼────────┼───────────┼────────┼────────┤
│ Req/Sec │ 12000 │ 14500 │ 24000 │ 26000 │ 23410.5 │ 2100 │ 11200 │
├───────────┼────────┼────────┼────────┼────────┼───────────┼────────┼────────┤
│ Bytes/Sec │ 2.4 MB │ 2.9 MB │ 4.8 MB │ 5.2 MB │ 4.68 MB │ 420 kB │ 2.2 MB │
└───────────┴────────┴────────┴────────┴────────┴───────────┴────────┴────────┘
Req/Bytes counts sampled once per second.
# 234k requests in 10.05s, 46.8 MB read
# 0 errors, 0 timeouts, 0 non-2xx responses
🔍 欄位詳細說明
⏱️ Latency (回應延遲時間,毫秒 ms)
- Avg (平均值):所有請求延遲的平均,容易受極端值干擾。
- 50% (中位數 / p50):一半的使用者回應時間快於此值。
- 97.5% / 99% (長尾延遲 / Tail Latency):🔥 最重要的穩定度指標。表示 $99%$ 的請求回應時間都在該數值以內。若
Avg只有 4ms 但p99突增至數百毫秒,代表伺服器存在偶發性阻塞(例如:垃圾回收 GC 停頓、DB connection pool 耗盡或 Event Loop 被阻塞)。 - Stdev (標準差):數值越低,說明系統回應越穩定均勻。
⚡ Req/Sec (每秒請求數 / QPS / TPS)
- Avg:伺服器平均每秒能處理的請求數。數字越高代表吞吐量越好。
🌐 Bytes/Sec (每秒網路流量吞吐)
每秒傳送的資料量。若 QPS 無法提升但 Bytes/Sec 已接近網卡限制,代表網卡頻寬滿載。
🏥 健康度統計 (最底部的數值)
errors:TCP/Socket 連線失敗或重置數量。timeouts:超過設定時間未獲得回應的請求數。non-2xx responses:收到 HTTP 4xx 或 5xx 的數量。⚠️ 特別注意:當伺服器壞掉直接回傳 500 時,QPS 可能會異常飆高,必須比對non-2xx是否不為 0。
四、 ⚙️ 啟動機制、setupClient 與 client 物件詳解
在 Node.js 中呼叫 autocannon(opts, [cb]) 時,執行方式取決於是否有傳入完成 Callback 函式。
1. 🔬 深度解析:什麼是 client 物件與 setupClient?
在選項中,setupClient 是一個用來初始化連線實例的 Callback 函式:
setupClient: (client) => {
// 💡 這個 client 代表其中一個「虛擬使用者 / TCP 持久連線」
}
(1) 🔌 什麼是 client 物件?
- 觀念:若你設定
connections: 10,autocannon 會在內部建立 10 個長連線 (HTTP Keep-Alive / TCP Socket) 實例。每一個實例就是一個client物件(本質上是一個 EventEmitter)。 - 生命週期:這 10 個
client物件會貫穿整個壓測過程。在 10 秒的壓測期間內,這 10 個client會各自不斷重複以下循環:[發送請求] ➔ [收到回應] ➔ [發送下一個請求] ➔ [收到回應] ... - 觸發時機:
setupClient函式會在每個client被建立時呼叫 1 次。若connections: 10,setupClient就會被呼叫 10 次,傳入 10 個不同的client實例。
(2) 🔄 client vs req 的關鍵區別
在監聽 client.on('request', (req) => ...) 時,很多人會混淆 client 與 req:
| 物件 | 代表意義 | 存在時間 | 核心用途 |
|---|---|---|---|
| client 🔌 | 連線通道 / 虛擬使用者 (EventEmitter) | 整個壓測生命週期 (只建立一次) | 用於監聽 request、response 或 error 事件 |
| req ✉️ | 當前這「一次」發送的 HTTP 請求 | 僅存在於該次請求發送前的瞬間 | 用於改寫 req.path (隨機 ID)、req.headers 或 req.body |
(3) 🛠️ client 支援的常用事件與屬性:
| 事件 / 屬性 | 說明 |
|---|---|
client.on('request', (req) => ...) |
⭐ 最常用。每次準備送出請求前觸發,傳入當前的 req 物件以修改請求細節 |
client.on('response', (statusCode, resBytes, responseTime) => ...) |
當該連線收到伺服器回應時觸發,可用於檢查狀態碼或紀錄延遲 |
client.on('error', (err) => ...) |
當該連線發生 TCP / Socket 底層錯誤時觸發 |
client.setHeaders(headers) |
設定該 client 後續所有請求預設帶有的 Header 物件 |
client.setBody(body) |
設定該 client 後續所有請求預設帶有的 Body 內容 |
2. 🤖 自動執行模式
只要呼叫 autocannon(opts, cb) 時傳入最後一個 Callback 函式,autocannon 在建立完實例後就會「自動開始壓測」。
配合 setupClient 回呼函式隨機產生 API ID 的完整範例:
import autocannon from 'autocannon';
const instance = autocannon({
url: '[https://your-api.com](https://your-api.com)',
connections: 10, // 會建立 10 個虛擬 Client,因此 setupClient 會被呼叫 10 次
duration: 10,
// setupClient 是一個 Callback 函式,參數 client 為這 10 個連線實例之一
setupClient: (client) => {
// 🎲 為這個 client 監聽 request 事件:每次它要發送 HTTP 請求前都會觸發此 Callback
client.on('request', (req) => {
// 1. 動態隨機產生 1 ~ 1000 的 API ID
const randomId = Math.floor(Math.random() * 1000) + 1;
// 2. 直接改寫這次請求的 path
req.path = `/api/users/${randomId}`;
// 3. (選擇性) 動態調整 Header,例如加上時間戳記
req.headers['x-request-timestamp'] = Date.now().toString();
});
// (選擇性) 監聽該 client 收到回應的事件
client.on('response', (statusCode, resBytes, responseTime) => {
if (statusCode >= 400) {
console.warn(`⚠️ 收到錯誤狀態碼: ${statusCode}`);
}
});
}
}, (err, result) => {
// 🎯 傳入這個完成 Callback 後,建立完 instance 就會「自動執行」壓測
if (err) return console.error('❌ 壓測出錯了:', err);
console.log(`⚡ 平均 QPS: ${result.requests.average}`);
console.log(`💥 錯誤的請求數量: ${result.errors}`);
});
3. 🎮 手動執行與事件監聽模式
如果不傳最後的完成 Callback,autocannon(opts) 會回傳一個 EventEmitter 實例 (instance)。你可以監聽多種事件(如 start、tick、done、reqError),並可在特定條件下呼叫 instance.stop() 主動結束壓測。
import autocannon from 'autocannon';
// 不傳最後的 Callback 函式,取得 instance 實例
const instance = autocannon({
url: '[https://your-api.com](https://your-api.com)',
connections: 20,
duration: 10,
setupClient: (client) => {
client.on('request', (req) => {
// 🎲 每次發送前動態產生亂數 ID
const randomProduct = Math.floor(Math.random() * 500) + 1;
req.path = `/api/products/${randomProduct}`;
});
}
});
// 🚀 監聽啟動事件
instance.on('start', () => {
console.log('🚀 壓測已開始...');
});
// ⏱️ 監聽每秒進度 (tick)
instance.on('tick', (stats) => {
// stats 包含該秒的單秒數據
});
// ❌ 監聽單一請求錯誤
instance.on('reqError', (err) => {
console.error('💥 發生請求錯誤:', err.message);
});
// 🎉 監聽完成事件 (取得最終結果)
instance.on('done', (result) => {
console.log('✅ 壓測完成!');
console.log(`📊 總請求數: ${result.requests.total}`);
console.log(`⏱️ p99 延遲: ${result.latency.p99} ms`);
});
// 🛑 手動控制:需要時呼叫 stop() 主動中止測試
setTimeout(() => {
if (/* 觸發緊急條件 */ false) {
console.log('⚠️ 手動中止壓測!');
instance.stop(); // 手動結束並觸發 done 事件
}
}, 3000);
4. 🎨 美觀輸出與進度條
不論是自動還是手動執行的 instance,都可以直接傳給 autocannon.track() 來渲染終端機進度條:
import autocannon from 'autocannon';
const instance = autocannon({
url: '[https://your-api.com](https://your-api.com)',
connections: 50,
duration: 10
}, (err, result) => {
if (err) console.error(err);
});
// 📊 直接把 instance 傳給 track 函數(自動或手動都可以處理)
autocannon.track(instance, { renderProgressBar: true });
5. ⚡ 現代化 Async / Await 封裝範例
在現代專案中,通常會以 Promise 將其封裝,配合 async/await 呼叫:
import autocannon from 'autocannon';
async function runBenchmark() {
console.log('🚀 開始執行壓測...');
const result = await new Promise((resolve, reject) => {
const instance = autocannon(
{
url: '[https://your-api.com](https://your-api.com)',
connections: 50,
duration: 10,
setupClient: (client) => {
client.on('request', (req) => {
// 🎲 動態隨機生成 API ID
const randomId = Math.floor(Math.random() * 5000) + 1;
req.path = `/api/products/${randomId}`;
});
}
},
(err, res) => (err ? reject(err) : resolve(res))
);
// 📊 掛載進度條
autocannon.track(instance, { renderProgressBar: true });
});
console.log('\n✅ 測試完成!');
console.log(`⚡ 平均 QPS: ${result.requests.average}`);
console.log(`⏱️ p99 延遲: ${result.latency.p99} ms`);
}
runBenchmark().catch(console.error);
五、 🎯 進階應用情境
1. ✉️ POST / 自訂 Header
import autocannon from 'autocannon';
const instance = autocannon({
url: '[https://your-api.com](https://your-api.com)',
connections: 50,
duration: 10,
method: 'POST',
headers: {
'content-type': 'application/json',
'authorization': 'Bearer your-dynamic-token'
},
body: JSON.stringify({
name: 'Test User',
email: 'test@example.com'
})
}, (err, result) => {
if (err) console.error(err);
else console.log('✅ 測試完成!');
});
2. 🔀 多重 API 端點管道測試 (requests 陣列)
按順序輪流發送不同的 API 請求:
import autocannon from 'autocannon';
const instance = autocannon({
url: '[https://your-api.com](https://your-api.com)',
connections: 10,
duration: 5,
requests: [
{
method: 'GET',
path: '/'
},
{
method: 'GET',
path: '/api/products',
headers: { 'x-custom-header': 'abc' }
},
{
method: 'POST',
path: '/api/checkout',
body: JSON.stringify({ id: 123 })
}
]
});
六、 🤖 CI/CD 自動化與效能門檻檢測 (Quality Gate)
將效能壓測納入 CI/CD 流程中,當 API 效能退化(如 p99 延遲過高或出現錯誤)時自動讓 Build 失敗。
建立一個驗證腳本 scripts/perf-test.js:
// scripts/perf-test.js
import autocannon from 'autocannon';
const THRESHOLDS = {
maxErrorRate: 0, // ❌ 錯誤數必須為 0
maxNon2xx: 0, // ⚠️ 非 2xx 狀態碼必須為 0
maxLatencyP99: 200, // ⏱️ p99 延遲不可超過 200ms
minAvgQps: 1000 // ⚡ 平均 QPS 必須大於等於 1000
};
async function runCiTest() {
console.log('🔍 正在執行 CI 效能門檻檢測...');
const result = await new Promise((resolve, reject) => {
const instance = autocannon(
{
url: process.env.TEST_TARGET_URL || 'http://localhost:3000/api/health',
connections: 50,
duration: 10
},
(err, res) => (err ? reject(err) : resolve(res))
);
autocannon.track(instance, { renderProgressBar: true });
});
console.log('\n--- 📊 壓測數據門檻驗證 ---');
let passed = true;
if (result.errors > THRESHOLDS.maxErrorRate) {
console.error(`❌ 失敗: 連線錯誤數 (${result.errors}) 超過門檻 (${THRESHOLDS.maxErrorRate})`);
passed = false;
}
if (result.non2xx > THRESHOLDS.maxNon2xx) {
console.error(`❌ 失敗: 非 2xx 回應數 (${result.non2xx}) 超過門檻 (${THRESHOLDS.maxNon2xx})`);
passed = false;
}
if (result.latency.p99 > THRESHOLDS.maxLatencyP99) {
console.error(`❌ 失敗: p99 延遲 (${result.latency.p99} ms) 超過門檻 (${THRESHOLDS.maxLatencyP99} ms)`);
passed = false;
}
if (result.requests.average < THRESHOLDS.minAvgQps) {
console.error(`❌ 失敗: 平均 QPS (${result.requests.average}) 低於門檻 (${THRESHOLDS.minAvgQps})`);
passed = false;
}
if (!passed) {
console.error('\n💥 效能測試未達標!');
process.exit(1);
}
console.log('\n🎉 所有效能門檻皆已順利通過!');
process.exit(0);
}
runCiTest().catch((err) => {
console.error('💥 執行壓測時發生意外錯誤:', err);
process.exit(1);
});
在 package.json 設定指令並執行:
{
"scripts": {
"test:perf": "node scripts/perf-test.js"
}
}
pnpm test:perf
七、 📈 報表輸出
除了終端機列印,也可以將結果存成報告:
1. 🌐 產生 HTML 視覺化報表 (CLI)
pnpm dlx autocannon [https://your-api.com](https://your-api.com) --report > report.html
2. 📄 輸出 JSON 格式供歷史紀錄分析
pnpm dlx autocannon [https://your-api.com](https://your-api.com) --json > result.json