使用 locust 和 uv 進行負載測試
這項技能能將專案從「它能承受多少?」轉變為一份團隊可以據以行動的數據報告。請依序執行每個步驟,因為每個步驟都為下一個步驟提供基礎。使用者需要做出兩個決定:測試等級和流量情境,而且只有在看到您在程式碼中發現的資訊後,才能做出決定。
$SKILL 是此技能所在的資料夾,也就是您讀取此 SKILL.md 檔案所在的目錄。請在 shell 中設定一次 (SKILL=/path/to/locust-load-test)。負載測試本身會存在於目標儲存庫的 loadtest/ 目錄中(或使用者選擇的任何位置)。
1 探索 -> loadtest/endpoints.json 每個端點,包含其呼叫者和成本提示
2 分類 -> loadtest/PLAN.md 核心/支援/邊緣表格;向使用者顯示
3 詢問 -> 等級 + 情境 + 環境 + 限制條件
4 建置 -> loadtest/ (一個 uv 專案) harness.py, payloads/, 執行腳本
5 執行 -> loadtest/results/<run>/ 先執行煙霧測試,然後是選定的等級,風險最高的最後執行
6 收集 -> results/<run>/metrics, profiles 伺服器端資訊(當可連接時)
7 報告 -> loadtest/report.html + 以白話文說明的發現
1. 探索端點
uv run $SKILL/scripts/discover_endpoints.py . -o loadtest/endpoints.json --markdown loadtest/endpoints.md
掃描器會讀取程式碼,並且只寫入此檔案。它可以找到跨常見技術堆疊的路由宣告:Python、Node、JVM、Go、Ruby、.NET 和 PHP 框架,以及 OpenAPI 檔案、GraphQL schema、gRPC protos、websockets 和 SSE。它會盡可能解析路由器前綴和掛載點。它還會計算每個端點的客戶端站點呼叫數量(fetch、axios、httpx、requests、生成的客戶端、測試,以及伺服器渲染模板中的連結、表單和 htmx 屬性),以便您了解產品本身的 UI 和服務使用了什麼。每個條目都包含方法、路徑、file:line、處理程式、認證提示、主體提示、串流提示、成本提示、處理程式進行的呼叫,以及誰呼叫了它。
框架自己的路由列表勝過任何靜態掃描,因為框架已經解析了所有前綴、掛載點和生成的路由。當應用程式可以產生一個時,請將其用作事實來源。這指的是來自運行中開發伺服器的 OpenAPI 文件 (/openapi.json)、flask routes、bin/rails routes、php artisan route:list --json,或 Spring 的 /actuator/mappings:
uv run $SKILL/scripts/discover_endpoints.py . --route-dump http://localhost:8000/openapi.json -o loadtest/endpoints.json
然後,在信任輸出之前,務必批判性地閱讀它:
unmatched_client_calls要不是掃描遺漏的路由,就是對其他服務的呼叫。prefix_resolved: false和notes標記了猜測。shared_handler: true表示一個函數服務於多種方法,而掃描無法分辨其成本提示屬於哪個方法;請閱讀該處理程式。existing_load_tests列出了儲存庫中已有的 locustfiles 或 k6 腳本。請重複使用它們的 payload。- 對照原始程式碼抽查五個條目。
當清單看起來很薄弱、某個技術堆疊未被涵蓋,或者您需要某個框架的路由轉儲命令時,請閱讀 references/discovery.md。
2. 分類:核心、支援、邊緣
根據判斷對端點進行分類。不要根據關鍵字分類:相同的 /search 在商店中是核心功能,但在管理控制檯中則是支援功能。
| 層級 | 它是什麼 | 典型範例 |
|---|---|---|
| 核心 | 使用者來此的目的,通常是繁重工作的所在地:計算、模型呼叫、資料處理、主要寫入路徑 | 商店的結帳流程;文件服務中的轉換或提取功能;聊天 API 中的完成功能 |
| 支援 | 使核心功能可用的功能 | 登入、工作階段、設定、使用者自身資料的清單和搜尋、為核心功能提供資料的上傳功能、管理 CRUD |
| 邊緣 | 產品並非為此而存在 | 健康檢查、指標、文件、靜態檔案、除錯路由、來自非所屬服務的 Webhook |
需要權衡的證據:
- README 和文件指出該產品的用途;
- UI 呼叫最多的內容 (
client_calls) 以及測試演練的內容 (test_calls); - 處理程式的成本:追蹤
calls進入服務層,因為cost_hints只能看到處理程式主體。模型呼叫、資料庫寫入、外部呼叫、CPU 工作(密碼雜湊、壓縮、圖片:cpu)、檔案處理和佇列發布是使端點昂貴的原因; - 命名方式;
- 如果可以查看,團隊自己的監控或日誌顯示最繁忙的內容。
撰寫 loadtest/PLAN.md,其中包含此表格,每個您打算進行負載測試的端點一行,以及一個簡短的、您排除的端點及其原因的列表:
| 端點 | 層級 | 原因 | 成本提示 | payload 來源 |
|---|---|---|---|---|
| POST /api/orders/{id}/checkout | 核心 | 購買路徑;UI 從 3 個地方呼叫 | 資料庫寫入,付款 API 呼叫 | tests/fixtures/order.json |
在提問之前向使用者顯示此表格,並邀請他們更正。錯誤的層級會使後續的每個數字都回答錯誤的問題,而使用者比任何掃描都更了解自己的產品。
3. 詢問使用者
使用 AskUserQuestion 工具。如果該工具不可用,請將相同的問題作為純編號列表提出,並等待回答。僅在使用者看到計劃表之後才提問。
- Q1 核心等級。 第 1、2 或 3 級。
- Q2 支援等級。 第 1、2 或 3 級,或者不測試支援功能。
- Q3 使用者負載情境。 從此專案推導出的二到四個選項(見下文),並將您推薦的選項標記為「(推薦)」,並用一句話說明它為何合適。
- Q4 目標環境。 要對哪個部署環境進行負載測試(staging、本機堆疊、負載測試副本)。除非使用者明確同意,否則切勿對生產系統進行負載測試。負載會擴散到共用的資料庫、租戶和帳單上。
| 層級 | 執行方式 | 回答的問題 | 成本 |
|---|---|---|---|
| 1 煙霧/基準測試 | 低穩定負載(一到幾個使用者)持續幾分鐘 | 每個路徑是否都能運作,在無競爭情況下的延遲是多少? | 幾分鐘;風險極低 |
| 2 預期負載 | 逐步增加到預期的峰值,保持該負載,然後在該水平進行短時間浸泡測試 | 在業務最繁忙時是否能滿足延遲目標,以及有多少緩衝空間? | 大約一小時;目標環境以其真實峰值運行 |
| 3 壓力/容量測試 | 逐步增加到觸發停止規則(瓶頸和崩潰點),外加一次尖峰測試 | 它在哪裡崩潰、如何崩潰,以及它能否恢復? | 一到兩小時;目標環境將會飽和並可能崩潰,因此需要一個時間窗口和有人在旁監控 |
從專案關於其流量的說明中推導出 Q3 的選項:README 和文件(誰使用它、席位數、租戶)、配置(速率限制、工作者和副本數量、自動擴展器邊界、佇列大小、連線池)、cron 和批次作業,以及呼叫者自己的逾時和重試設定。常見的形狀有:上班時間的逐步增加、早晨的尖峰、穩定的浸泡測試、來自其他服務的整合突發、批次匯入,以及混合的互動式加背景流量。每個選項都應說明其數字:使用者數、生成速率、思考時間、持續時間和請求混合。當專案對其流量隻字未提時,請說明這一點,指出每個數字所依據的假設,並在 Q3 中詢問預期的峰值。
在提問之前閱讀 references/tiers-and-scenarios.md。它展示了如何將配置轉換為這些數字。
在得到答案後,以純文字確定兩件事:
- 憑證。 它們來自一個檔案或環境變數,由測試工具讀取。切勿將它們貼到聊天中。
- 限制條件。 列出您發現的並詢問其餘的:速率限制、許可證或啟用窗口、重新啟動的成本、共用租戶,以及每個請求的成本(模型 token、付費 API)。在許可證窗口內重新啟動可能會將服務鎖定一天,而幾個平行的繁重作業加上一個巨大的輸入可能會使目標環境癱瘓。在負載測試之前,而不是之後詢問。
- 資料狀態。 列表和搜尋在實際使用中容納了多少行資料、資料是否可以在測試運行之間重置,以及測試使用者可以編輯或刪除哪些使用者的行。空的表格會使列表頁面變得免費,而測試運行添加的行會使後續的每個步驟讀取比以前更多的資料。
4. 建置測試工具
mkdir -p loadtest && cd loadtest
cp $SKILL/templates/pyproject.toml . && uv sync # 專屬的 .venv 和 uv.lock;locust 版本鎖定,Python 3.11+
cp $SKILL/templates/{harness.py,run_local.sh,run_plan.sh,collect.sh,seed.py,loadtest.env.example} .
cp $SKILL/templates/run_docker.sh . # 當生成器在目標環境的 Docker 網路上運行時
cp -r $SKILL/templates/{run_k8s.sh,k8s} . # 當它在目標叢集內運行時
git -C .. status --short # 只允許出現 loadtest/
請勿在此運行 uv init。在一個本身就是 uv 專案的儲存庫中,它會將 loadtest 添加到應用程式的 [tool.uv.workspace],將 locust 安裝到應用程式的 .venv 中,並寫入應用程式層級的 uv.lock。如果發生這種情況,請復原:在應用程式中執行 git checkout -- pyproject.toml,刪除新的 uv.lock,然後在那裡運行 uv sync。然後填入範本:
REQUESTS. 根據 PLAN.md 填入harness.py中的表格:層級、名稱、權重、混合、主體和預期狀態。從專案的測試、fixtures 和 schemas 中獲取 payload,並將它們放入loadtest/payloads/。切勿使用生產資料。伺服器渲染的應用程式會為其登入頁面和錯誤頁面都回答 200,因此請檢查僅在正確頁面上才有的內容:expect_text,以及在重定向是正確答案時檢查expect_location。被重定向但未說明的行將會失敗。REQ_TIMEOUT. 將其設定為真實呼叫者的逾時時間:閘道器、前端的 abort、客戶端 SDK 或代理的讀取逾時。測試工具將每個請求都保持到此逾時,作為完整答案的截止時間,因此較晚的答案會以「超過截止時間」失敗,而不是被計入成功(而產品早就將其報告為失敗)。MIX. 為每個具有不同成本的流量混合提供自己的標籤,並將每個混合作為自己的情境運行(短聊天大小的輸入、文件大小的輸入、一個成本更高的選項)。它們的容量可能相差數倍,而混合運行會隱藏昂貴的那個。- Auth. 設定
AUTH,使每個模擬使用者登入一次,而不是每個請求都登入一次。帶有 session cookie 的表單登入也需要LOGIN_EXPECT_LOCATION或LOGIN_EXPECT_TEXT,因為錯誤的密碼也會回答 200(請參閱loadtest.env.example)。登入會進入login階段,並排除在步驟編號和停止規則之外:每個登入都會在添加其使用者的步驟開始時到達,而緩慢的密碼雜湊處理會以穩定負載從不支付的成本結束運行。要對登入本身進行負載測試,請將其設為一行。 - 資料. 為應用程式填入
seed.py並運行python3 seed.py up --users N --rows M:通過應用程式的 API,每個測試帳戶都會獲得自己的行,SEED_MANIFEST會將其分配給其使用者;行鍵capture和consume追蹤使用者建立和刪除的內容。將建立與刪除配對,或在運行之間重新播種。 - 流程. 多步驟流程(上傳然後輪詢、建立然後讀取)是使用者類別上的一個方法;範本中有一個範例。
在構建時閱讀 references/harness.md。它涵蓋了每個欄位、形狀、認證模式、串流、非 HTTP 協定和三種部署方式。
5. 安全地執行
- 先執行煙霧測試。 運行
RUN_NAME=smoke SHAPE=smoke ./run_local.sh:每個使用者類別一個使用者,按表格順序發送每一行(將建立 id 的行放在使用它的行之前),然後空閒。日誌結尾會顯示NOTE smoke: every row was sent,或者列出未發送的行。檢查零失敗、合理的延遲和正確的主體。這證明了連線正常,在產生任何負載成本之前。 - 檢查機器是否空閒。 在執行測量容量的運行之前,在生成器和目標環境的主機上運行
uptime和vmstat 1 5。同一核心上的其他作業會使每個數字都成為爭用的度量。測試工具在每次運行之前會將自身主機的取樣記錄到run.json(host_check和一個註解);當目標主機是另一台機器時,將其數字放入NOTES。報告會在一個運行期間其主機或生成器 CPU 在其步驟中達到 90% 時加上橫幅,並對其瓶頸值提出警告。如果機器正忙,請等待、固定到空閒核心 (taskset),或移動,並說明是哪種情況。 - 然後執行選定的層級,從最安全開始。 先執行預期負載,然後是浸泡測試,然後是壓力測試,最後是尖峰測試。風險較高的輸入(最大的 payload、成本更高的選項)最後執行,這樣即使它們對目標造成損害,之前的測試結果也不會遺失。
run_plan.sh按順序運行列表,並且僅在目標的健康檢查通過時才開始每個運行。 - 當網路路徑很重要時,將生成器放在真實呼叫者旁邊:同一個叢集、VNet 或 compose 網路 (
run_k8s.sh,run_docker.sh)。筆記型電腦上的運行測量的是網際網路和服務本身。每個運行都會記錄GENERATOR,生成器自身的 CPU 會寫入metrics/client.csv;當 CPU 超過約 90% 時,數字描述的是生成器。 - 遵守停止規則。 當失敗比率超過
STOP_FAIL_RATIO、當 p95 超過STOP_P95_MS、當過去 20 秒內超過一半的請求失敗,或者當大多數登入失敗時,測試工具會結束運行。許多服務會對請求進行排隊而不是丟棄負載:零錯誤,而延遲只會增長。因此,請將 p95 上限設定為呼叫者的逾時時間或 SLO。不要為了「看看會怎樣」而強行越過停止規則。 - 在運行之間監控健康狀態,並讓佇列排空 (
run_plan.sh中的COOLDOWN)。 - 清理您建立的所有內容:
./run_k8s.sh down(pod、ConfigMaps、Secret)、已停止的容器、測試使用者和測試資料。為使用者列出任何無法移除的內容,例如軟刪除的行或稽核條目。
在執行煙霧測試之外的第一次運行之前,以及在對共用系統進行任何運行之前,請閱讀 references/safety.md。
6. 收集伺服器端資訊
客戶端數字說明發生了什麼。伺服器端數字說明為什麼。在 locust 之前啟動收集器,在 locust 之後停止它們,並寫入同一個運行目錄:
uv run $SKILL/scripts/collect_metrics.py run_id
如果目標伺服器可透過 SSH 或 Kubernetes API 訪問,此腳本會收集:
top和vmstat的快照;- 每個端點的 Nginx/HAProxy 指標(如果存在);
- 應用程式日誌的最後 N 行;
- 資料庫連線池狀態。
否則,它會提示使用者手動收集這些資料並將其放入 results/<run>/。
7. 報告
uv run $SKILL/scripts/generate_report.py loadtest/results/<run> -o loadtest/report.html
報告包含:
- 執行摘要(測試了什麼、發現了什麼);
- 容量估計(瓶頸點、崩潰點、恢復能力);
- 每個端點的延遲分佈圖;
- 伺服器端資源使用情況(CPU、記憶體、I/O);
- 失敗模式分析;
- 可操作的建議。
用白話文解釋結果,專注於團隊可以用來改善效能和容量的具體行動。