身分驗證與售票流程開發完畢後就可以先來做個簡單的壓力測試了,進行壓力測試當然要有合適的工具來進行,壓力測試的工具有非常多種,如 Jmeter、Locust、K6 等等,其中歷史最悠久最多人使用的應該就是 Jmeter ,但 Jmeter 對硬體效能的要求較高,我的個人電腦可能壓不到系統的上限,Locust 跟 K6 都是相對較新架構的壓測工具,使用起來差異也不大,個人較熟悉 K6,今天就選用 K6 來進行壓力測試。
以下我們一樣有請 Gemini 來跟我們簡單的介紹一下 K6
K6 是一款功能強大且易於使用的負載測試工具,專門用於測試軟體系統在高流量或高負載情況下的性能表現。它採用 JavaScript 作為腳本語言,讓開發人員能夠輕鬆地撰寫複雜的測試。
K6 是開源的專案,依照自己的 OS 直接到 Github 上抓取最新 Release 的版本
Releases · grafana/k6 (github.com)
安裝好後在 Terminal 輸入 K6 就可以看到相關的參數了
K6 是使用 Javascript 來撰寫腳本的,在任意一個位置建立一個 .js 檔案
import http from 'k6/http';
export const options = {
vus: 1,
iterations: 1
};
export default function () {
http.get('https://[SERVICE_URL]/PubSubTest');
}
測試執行看看
k6 run script.js
可以看到成功送出了 1 個 Request ,並且有詳細的時間資訊。
簡單說明一下腳本的內容,最上方的 options 是要給 k6 的參數,詳細的參數可以使用 help 來查看有哪些
k6 run help
可以在 script 最上方指定,也可以在執行腳本的 command 後在加入參數,要注意的是 command 所下的參數是會覆蓋掉 script 內部的參數的。
以下列出一些常用的參數及說明
這邊只會用到前三個參數,要測試之前要準備好很多的帳號跟一個有很多座位的活動,當然這些可以透過人工建立或是透過其他工具來處理,也可以直接透過 k6 來完成這些要求。
我們這裡就用一個 for 迴圈慢慢去建立 100 個帳號
import http from 'k6/http';
export const options = {
vus: 1,
iterations: 1
};
const baseUrl = 'https://[SERVICE_URL]';
const arrayLength = 1000;
const resultArray = [];
for (let i = 0; i < arrayLength; i++) {
resultArray.push({
username: 'Test' + i,
password: 'Test' + i
});
}
export default function () {
const vuId = __VU;
for (let i = 0; i < arrayLength; i++) {
http.post(baseUrl + '/api/auth/user', JSON.stringify(
resultArray[i]
), {
headers: {
'Content-Type': 'application/json',
},
});
}
}
檢查是否有建立成功
這裡一樣用 1 個 vus 執行一次,建立一個有 60000 個位子的活動
import http from 'k6/http';
export const options = {
vus: 1,
duration: '30s',
iterations: 1
};
const baseUrl = 'https://ithome2024-salesservice-539812124803.asia-east1.run.app';
const arrayLength = 60000;
const resultArray = [];
for (let i = 0; i < arrayLength; i++) {
resultArray.push({
name: i.toString(),
});
}
export default function () {
var res = http.post(baseUrl + '/api/event', JSON.stringify({
"name": "TestEvent001",
"eventDate": "2024-09-25T15:15:39",
"startSalesDate": "2024-09-25T15:15:39",
"endSalesDate": "2024-09-25T15:15:39",
"description": "Desc",
"remark": "Re",
"seats": resultArray
}), {
headers: {
'Content-Type': 'application/json',
},
});
console.log(res.status);
}
檢查是否有建立成功
OK 這樣壓力測試的事前資料就準備好了
購票的 script 會稍微複雜些,因為我們每個 vus 都需要登入,登入後取得 token 再進行購票,而購票前也要先取得座位的資訊才能購票,可以看到這裡我們新增了一個 setup 的 function 這個 function 是開始壓測前會先執行的 script 可以在裡面準備好一些要給 vus 重複使用的資訊。
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
vus: 100,
duration: '30s',
};
const baseUrl = 'https://ithome2024-salesservice-539812124803.asia-east1.run.app';
const eventId = 21;
const vuData = {
tokens: [],
seats: []
};
export function setup() {
var res = http.get(baseUrl + '/api/event/' + eventId);
var event = JSON.parse(res.body);
vuData.seats = event.seats;
for (let i = 0; i < options.vus; i++) {
let res = http.post(baseUrl + '/api/auth', JSON.stringify({
username: 'Test' + i,
password: 'Test' + i
}), {
headers: {
'Content-Type': 'application/json',
},
});
vuData.tokens.push(JSON.parse(res.body).token);
}
return vuData
}
export default function (data) {
const randomIndex = Math.floor(Math.random() * data.seats.length);
// const randomSeat = data.seats[randomIndex];
let body = {
"eventId": eventId,
"seatId": data.seats[randomIndex].id,
"createTime": "2024-09-25T15:15:39"
};
let res = http.post(baseUrl + '/api/ticket', JSON.stringify(body), {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + data.tokens[__VU - 1]
},
});
if (res.status !== 201) {
console.log(res.status);
console.log('request Body: ' + JSON.stringify(body));
console.log('response body: ' + res.body);
}
}
壓完之後可以看到 RPS 慘不忍睹只有 27,min 得 http wating 只有 77 毫秒,但是 p95 有道8秒非常之長,平均也要 1.8秒左右,很明顯搶票 API 目前效能是很差的 100vus 都無法應付。
購票的 API 只有跟 Redis 有IO互動以及最後推送購票資訊給 Pub/Sub,理論上 Redis 跟 Pus/Sub 都是是能夠承受到 百萬級別的 QPS 的,後續要再針對購票的 API 來做調整,找出問題在哪裡並持續的優化它。