負載測試場景設計
概述
本文檔詳細定義了電商平台的關鍵負載測試場景,涵蓋客戶購物流程、管理員操作、即時通知系統和 JSONB 快照效能等核心業務場景。每個場景都包含具體的測試腳本、預期指標和故障排除方法。
測試場景分類
高頻場景 (80% 流量)
- 客戶瀏覽商品
- 加入購物車
- 客服即時對話
中頻場景 (15% 流量)
- 客戶註冊登入
- 訂單建立結帳
- 管理員訂單處理
低頻場景 (5% 流量)
- 庫存大量更新
- 系統報告生成
- 權限角色管理
🛒 場景 1: 客戶完整購物旅程
場景描述
模擬真實客戶從註冊到完成購買的完整流程,測試前後台系統的協調性和 JSONB 快照生成效能。
負載參數
yaml
# artillery-customer-journey.yml
config:
target: 'http://localhost:5173' # front-stage-vue
phases:
- duration: 300 # 5分鐘爬升期
arrivalRate: 1
rampTo: 20
- duration: 900 # 15分鐘穩定期
arrivalRate: 20
- duration: 300 # 5分鐘降低期
arrivalRate: 20
rampTo: 1
processor: './customer-journey-functions.js'
variables:
categories: ['electronics', 'clothing', 'home', 'books']
paymentMethods: ['credit_card', 'paypal', 'bank_transfer']
scenarios:
- name: "完整購物旅程"
weight: 100
flow:
# 1. 首頁瀏覽
- get:
url: "/"
capture:
- regex: 'sessionId":"([^"]*)"'
as: sessionId
# 2. 客戶註冊
- function: "generateCustomerData"
- post:
url: "/api/auth/register"
json:
name: "{{ customerName }}"
email: "{{ customerEmail }}"
password: "CustomerPass123!"
preferences:
newsletter: true
category: "{{ $randomString(categories) }}"
capture:
- json: "$.user.id"
as: customerId
- json: "$.access_token"
as: accessToken
expect:
- statusCode: 201
# 3. 瀏覽商品分類
- think: 2
- get:
url: "/api/products"
qs:
category: "{{ $randomString(categories) }}"
page: 1
limit: 20
headers:
Authorization: "Bearer {{ accessToken }}"
# 4. 查看商品詳細頁
- function: "selectRandomProduct"
- think: 5
- get:
url: "/api/products/{{ productId }}"
headers:
Authorization: "Bearer {{ accessToken }}"
capture:
- json: "$.price"
as: productPrice
- json: "$.inventory.quantity"
as: availableStock
# 5. 加入購物車
- think: 3
- post:
url: "/api/cart/add"
headers:
Authorization: "Bearer {{ accessToken }}"
json:
product_id: "{{ productId }}"
quantity: "{{ $randomInt(1, 3) }}"
expect:
- statusCode: 200
# 6. 檢視購物車
- think: 2
- get:
url: "/api/cart"
headers:
Authorization: "Bearer {{ accessToken }}"
capture:
- json: "$.total_amount"
as: cartTotal
# 7. 結帳流程
- think: 10 # 用戶考慮時間
- post:
url: "/api/orders/create"
headers:
Authorization: "Bearer {{ accessToken }}"
json:
payment_method: "{{ $randomString(paymentMethods) }}"
shipping_address:
street: "{{ $randomString(['123 Main St', '456 Oak Ave', '789 Pine Rd']) }}"
city: "{{ $randomString(['Taipei', 'Kaohsiung', 'Taichung']) }}"
postal_code: "{{ $randomInt(10000, 99999) }}"
special_instructions: "測試訂單 - {{ sessionId }}"
capture:
- json: "$.order.id"
as: orderId
- json: "$.order.order_number"
as: orderNumber
expect:
- statusCode: 201
# 8. 確認訂單建立 (驗證 JSONB 快照)
- think: 1
- get:
url: "/api/orders/{{ orderId }}"
headers:
Authorization: "Bearer {{ accessToken }}"
expect:
- statusCode: 200
- json: "$.jsonb_snapshots.product_snapshot"
- json: "$.jsonb_snapshots.pricing_snapshot"
# 9. 客服諮詢 (30% 機率)
- function: "shouldContactSupport" # 返回 true 30% 機率
- ifTrue:
- post:
url: "/api/conversations"
headers:
Authorization: "Bearer {{ accessToken }}"
json:
subject: "訂單相關問題 - {{ orderNumber }}"
message: "請問我的訂單什麼時候會出貨?"
order_id: "{{ orderId }}"
capture:
- json: "$.conversation.id"
as: conversationId自定義函數
javascript
// customer-journey-functions.js
const faker = require('@faker-js/faker')
module.exports = {
generateCustomerData,
selectRandomProduct,
shouldContactSupport
}
function generateCustomerData(context, events, done) {
context.vars.customerName = faker.name.fullName()
context.vars.customerEmail = `test-${Date.now()}-${Math.random().toString(36).substr(2, 9)}@loadtest.com`
return done()
}
function selectRandomProduct(context, events, done) {
// 從前一個產品 API 回應中選擇產品
const products = context.vars.$response?.data || []
if (products.length > 0) {
const randomProduct = products[Math.floor(Math.random() * products.length)]
context.vars.productId = randomProduct.id
} else {
// 備用產品 ID
context.vars.productId = Math.floor(Math.random() * 50) + 1
}
return done()
}
function shouldContactSupport(context, events, done) {
context.vars.shouldContactSupport = Math.random() < 0.3
return done()
}預期效能指標
- 註冊 API: < 2 秒
- 商品查詢: < 800ms
- 購物車操作: < 500ms
- 訂單建立: < 3 秒 (包含 JSONB 快照生成)
- 整體旅程: < 20 秒
👨💼 場景 2: 管理員高頻操作
場景描述
模擬管理員在後台系統進行訂單處理、庫存管理、客服回覆等高頻操作,測試後台系統在高負載下的穩定性。
測試腳本
yaml
# artillery-admin-operations.yml
config:
target: 'http://localhost:5174' # admin-platform-vue
phases:
- duration: 120
arrivalRate: 5
- duration: 600
arrivalRate: 15
- duration: 120
arrivalRate: 5
processor: './admin-operations-functions.js'
scenarios:
- name: "管理員登入"
weight: 5
flow:
- post:
url: "/api/auth/login"
json:
email: "admin@example.com"
password: "AdminPass123!"
capture:
- json: "$.access_token"
as: adminToken
- json: "$.user.id"
as: adminId
- name: "訂單處理作業"
weight: 40
flow:
- function: "authenticateAdmin"
# 查看待處理訂單
- get:
url: "/api/admin/orders"
headers:
Authorization: "Bearer {{ adminToken }}"
qs:
status: "pending"
limit: 50
capture:
- json: "$.data[0].id"
as: orderId
# 查看訂單詳細
- get:
url: "/api/admin/orders/{{ orderId }}"
headers:
Authorization: "Bearer {{ adminToken }}"
expect:
- statusCode: 200
- json: "$.jsonb_snapshots" # 驗證快照完整性
# 更新訂單狀態
- patch:
url: "/api/admin/orders/{{ orderId }}/status"
headers:
Authorization: "Bearer {{ adminToken }}"
json:
status: "processing"
notes: "訂單已確認,開始處理"
expect:
- statusCode: 200
- name: "庫存管理操作"
weight: 25
flow:
- function: "authenticateAdmin"
# 檢視低庫存商品
- get:
url: "/api/admin/inventory/low-stock"
headers:
Authorization: "Bearer {{ adminToken }}"
capture:
- json: "$.data[0].product_id"
as: lowStockProductId
# 補充庫存
- post:
url: "/api/admin/inventory/restock"
headers:
Authorization: "Bearer {{ adminToken }}"
json:
product_id: "{{ lowStockProductId }}"
quantity: "{{ $randomInt(50, 200) }}"
reason: "負載測試補貨"
expect:
- statusCode: 200
- name: "客服回覆處理"
weight: 30
flow:
- function: "authenticateAdmin"
# 獲取待處理對話
- get:
url: "/api/admin/conversations/pending"
headers:
Authorization: "Bearer {{ adminToken }}"
capture:
- json: "$.data[0].id"
as: conversationId
# 客服回覆
- post:
url: "/api/admin/conversations/{{ conversationId }}/reply"
headers:
Authorization: "Bearer {{ adminToken }}"
json:
message: "感謝您的諮詢,我們會儘快為您處理。"
is_internal: false
agent_id: "{{ adminId }}"
expect:
- statusCode: 201
# 標記對話為已處理
- patch:
url: "/api/admin/conversations/{{ conversationId }}"
headers:
Authorization: "Bearer {{ adminToken }}"
json:
status: "resolved"預期效能指標
- 登入驗證: < 1 秒
- 訂單查詢: < 1.5 秒
- 庫存更新: < 800ms
- 客服回覆: < 1 秒
場景 3: 即時通知系統壓力測試
場景描述
測試 WebSocket 連接在高並發情況下的穩定性,以及即時通知系統的延遲表現。
WebSocket 負載測試
javascript
// k6-websocket-load.js
import ws from 'k6/ws';
import { check } from 'k6';
export const options = {
vus: 50, // 50個並發用戶
duration: '10m', // 測試10分鐘
};
export default function () {
const url = 'ws://localhost:5174/ws';
const params = {
headers: {
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...' // 測試用 token
}
};
const res = ws.connect(url, params, function (socket) {
let notificationCount = 0;
let totalLatency = 0;
socket.on('open', () => {
console.log('WebSocket 連接建立');
// 訂閱通知頻道
socket.send(JSON.stringify({
type: 'subscribe',
channel: 'notifications',
userId: `test-user-${__VU}`
}));
});
socket.on('message', (data) => {
const message = JSON.parse(data);
if (message.type === 'notification') {
notificationCount++;
// 計算延遲 (如果訊息包含時間戳)
if (message.timestamp) {
const latency = Date.now() - message.timestamp;
totalLatency += latency;
}
}
});
socket.on('close', () => {
const avgLatency = notificationCount > 0 ? totalLatency / notificationCount : 0;
check(null, {
'收到通知數量 > 0': () => notificationCount > 0,
'平均延遲 < 2000ms': () => avgLatency < 2000,
});
});
// 保持連接並定期發送心跳
socket.setTimeout(() => {
socket.ping();
}, 30000);
// 模擬用戶活動觸發通知
setTimeout(() => {
socket.send(JSON.stringify({
type: 'activity',
action: 'view_product',
productId: Math.floor(Math.random() * 100) + 1
}));
}, 5000);
});
check(res, { 'WebSocket 連接成功': (r) => r && r.status === 101 });
}預期效能指標
- WebSocket 連接建立: < 100ms
- 通知延遲: < 2 秒
- 連接穩定性: 99.5% uptime
- 並發連接數: 支援 200+ 同時連接
場景 4: JSONB 快照系統效能
場景描述
專門測試 JSONB 快照系統在大量訂單建立時的效能表現,確保資料完整性不影響系統回應速度。
資料庫壓力測試
sql
-- performance-tests/jsonb-snapshot-load.sql
-- 建立測試用的大量訂單函數
CREATE OR REPLACE FUNCTION load_test_create_bulk_orders(
batch_size INTEGER DEFAULT 100,
customer_count INTEGER DEFAULT 50
) RETURNS jsonb AS $$
DECLARE
start_time TIMESTAMP;
end_time TIMESTAMP;
avg_response_time NUMERIC;
snapshot_check_result JSONB;
order_record RECORD;
i INTEGER;
BEGIN
start_time := clock_timestamp();
-- 批次建立訂單
FOR i IN 1..batch_size LOOP
INSERT INTO orders (
customer_id,
order_number,
total_amount,
status,
created_at
)
SELECT
(RANDOM() * customer_count)::INTEGER + 1,
'LOAD-TEST-' || LPAD(i::TEXT, 6, '0'),
(RANDOM() * 1000 + 50)::NUMERIC(10,2),
'pending',
NOW()
FROM generate_series(1, 1);
END LOOP;
end_time := clock_timestamp();
avg_response_time := EXTRACT(EPOCH FROM (end_time - start_time)) * 1000 / batch_size;
-- 驗證 JSONB 快照完整性
SELECT jsonb_build_object(
'total_orders', COUNT(*),
'orders_with_snapshots', COUNT(*) FILTER (WHERE jsonb_snapshots IS NOT NULL),
'snapshot_completeness_rate',
ROUND(
(COUNT(*) FILTER (WHERE jsonb_snapshots IS NOT NULL)::NUMERIC / COUNT(*)) * 100,
2
)
) INTO snapshot_check_result
FROM orders
WHERE order_number LIKE 'LOAD-TEST-%';
RETURN jsonb_build_object(
'batch_size', batch_size,
'avg_response_time_ms', avg_response_time,
'snapshot_integrity', snapshot_check_result,
'performance_grade',
CASE
WHEN avg_response_time < 100 THEN 'A'
WHEN avg_response_time < 500 THEN 'B'
WHEN avg_response_time < 1000 THEN 'C'
ELSE 'D'
END
);
END;
$$ LANGUAGE plpgsql;
-- 執行壓力測試
SELECT load_test_create_bulk_orders(500, 100);
-- 清理測試資料
DELETE FROM orders WHERE order_number LIKE 'LOAD-TEST-%';k6 資料庫連接測試
javascript
// k6-database-load.js
import sql from 'k6/x/sql';
import { check } from 'k6';
export const options = {
vus: 20,
duration: '5m',
};
const db = sql.open('postgres', 'postgresql://postgres:postgres@localhost:54322/postgres');
export default function () {
// 測試大量 JSONB 查詢
const result = sql.query(db, `
SELECT
id,
jsonb_snapshots->'product_snapshot' as products,
jsonb_snapshots->'pricing_snapshot' as pricing,
extract(epoch from (now() - created_at)) * 1000 as age_ms
FROM orders
WHERE created_at > now() - interval '1 hour'
AND jsonb_snapshots IS NOT NULL
ORDER BY created_at DESC
LIMIT 50
`);
check(result, {
'JSONB 查詢成功': (r) => r.length >= 0,
'JSONB 查詢速度 < 500ms': (r) => {
// 簡單的延遲檢查
const start = Date.now();
JSON.parse(JSON.stringify(r));
return (Date.now() - start) < 500;
}
});
}
export function teardown() {
db.close();
}預期效能指標
- 單一訂單建立: < 200ms
- 批次訂單建立: < 100ms/筆 平均
- JSONB 快照完整性: 100%
- 複雜 JSONB 查詢: < 500ms
場景 5: 綜合壓力測試
場景描述
結合所有場景的混合負載測試,模擬真實的生產環境流量分佈。
混合場景配置
yaml
# artillery-mixed-load.yml
config:
target: 'http://localhost:5174'
phases:
- duration: 600 # 10分鐘
arrivalRate: 10
name: "爬升階段"
- duration: 1800 # 30分鐘
arrivalRate: 50
name: "穩定負載階段"
- duration: 600 # 10分鐘
arrivalRate: 10
name: "降低階段"
scenarios:
# 高頻場景 - 80% 流量
- name: "商品瀏覽"
weight: 50
flow:
- get:
url: "/api/products"
qs:
page: "{{ $randomInt(1, 20) }}"
- name: "購物車操作"
weight: 20
flow:
- function: "simulateAuthentication"
- post:
url: "/api/cart/add"
headers:
Authorization: "Bearer {{ userToken }}"
json:
product_id: "{{ $randomInt(1, 100) }}"
quantity: "{{ $randomInt(1, 3) }}"
- name: "即時客服"
weight: 10
flow:
- function: "simulateAuthentication"
- post:
url: "/api/conversations"
headers:
Authorization: "Bearer {{ userToken }}"
json:
message: "我想詢問產品相關問題"
# 中頻場景 - 15% 流量
- name: "客戶註冊"
weight: 8
flow:
- function: "generateCustomerData"
- post:
url: "/api/auth/register"
json:
name: "{{ customerName }}"
email: "{{ customerEmail }}"
password: "TestPass123!"
- name: "訂單建立"
weight: 5
flow:
- function: "simulateAuthentication"
- post:
url: "/api/orders/create"
headers:
Authorization: "Bearer {{ userToken }}"
json:
items: [
{
product_id: "{{ $randomInt(1, 100) }}",
quantity: "{{ $randomInt(1, 2) }}"
}
]
- name: "管理員操作"
weight: 2
flow:
- function: "authenticateAdmin"
- get:
url: "/api/admin/orders"
headers:
Authorization: "Bearer {{ adminToken }}"
# 低頻場景 - 5% 流量
- name: "庫存更新"
weight: 3
flow:
- function: "authenticateAdmin"
- post:
url: "/api/admin/inventory/restock"
headers:
Authorization: "Bearer {{ adminToken }}"
json:
product_id: "{{ $randomInt(1, 50) }}"
quantity: "{{ $randomInt(10, 100) }}"
- name: "報告生成"
weight: 2
flow:
- function: "authenticateAdmin"
- get:
url: "/api/admin/reports/daily-summary"
headers:
Authorization: "Bearer {{ adminToken }}"測試執行清單
預測試檢查
- [ ] 所有應用服務正常運行
- [ ] 資料庫連接穩定
- [ ] 測試資料已初始化
- [ ] 監控工具已啟動
- [ ] 備份點已建立
執行步驟
bash
# 1. 環境準備
npm run test:env:setup
# 2. 輕量級冒煙測試
artillery run performance-tests/smoke-test.yml
# 3. 單一場景測試
artillery run performance-tests/artillery-customer-journey.yml
artillery run performance-tests/artillery-admin-operations.yml
k6 run performance-tests/k6-websocket-load.js
# 4. 資料庫專項測試
psql -f performance-tests/jsonb-snapshot-load.sql
# 5. 綜合壓力測試
artillery run performance-tests/artillery-mixed-load.yml
# 6. 結果分析
npm run test:analyze-results後測試檢查
- [ ] 服務狀態確認
- [ ] 錯誤日誌檢查
- [ ] 效能指標分析
- [ ] 資料完整性驗證
- [ ] 清理測試資料
🚨 故障場景測試
模擬故障情況
yaml
# artillery-chaos-test.yml
config:
target: 'http://localhost:5174'
phases:
- duration: 300
arrivalRate: 20
scenarios:
- name: "資料庫連接中斷模擬"
weight: 30
flow:
# 正常請求後立即大量請求
- get:
url: "/api/products"
- loop:
count: 10
over: "request"
whileTrue: "template"
each:
- post:
url: "/api/orders"
json:
customer_id: "{{ $randomInt(1, 100) }}"
items: []
ifTrue: "template"
- name: "記憶體洩漏模擬"
weight: 20
flow:
- loop:
count: 100
each:
- get:
url: "/api/products?include=inventory,reviews,related"相關文檔
最後更新: $(date "+%Y-%m-%d")
測試工具版本: Artillery 2.x, k6 0.46+, PostgreSQL 15