1. 前言
通過之前的學習,我們已經掌握了crank的配置以及對應http基準工具bombardier、wrk、wrk2的用法,本篇文章介紹一下如何將其用於實戰,在實際的專案中我們如何使用crank來完成壓測任務。
2. 專案背景
目前有一個專案,我們希望通過壓測來了解其QPS、吞吐量、以及臨界值,並通過壓測來分析出其瓶頸在哪裡?並通過解決瓶頸問題以提高QPS、吞吐量等指標
先看下我們手頭掌握了什麼:
- 專案資訊
- 專案中的介面基本都需要登入
- 通過與開發溝通可以得到每個頁面的介面資訊以及引數資訊
- 環境資訊
- 壓測專案有單獨的環境部署應用、Redis、資料庫等基礎配置
此處專案名我們暫定為ProjectA。
3. 如何開展
首先我們先回顧一下Agent、Controller的職責以及特點
- Controller
- 做任務排程以及結果輸出
- 無需單獨伺服器,可以在本機執行傳送命令,需要與Agent相通
- Agent
- 任務的實際執行者
- 單任務執行,不能做到接收到多個任務並同時執行,先收到哪個任務,哪個任務會先執行
- 相同一個任務可以被多個Agent同時執行,最終指標結果會自動累加,可以通過提升Agent來模擬更高的併發能力
3.1. 思路
- 先做好單獨介面的壓測,大概掌握每個介面的指標情況
- 同時壓測多個介面,完成對場景的壓測
- 通過壓測觀察應用伺服器、基礎伺服器的CPU、頻寬、記憶體等指標,觀察Redis、資料庫、訊息佇列等基礎元件情況,根據壓測的返回結果得到每個場景的基礎指標
- 通過分析發現瓶頸、然後再考慮如何突破瓶頸,提升QPS、吞吐量等
3.2. 如何做?
瞭解到單個Agent同時執行多個任務會進行排隊,無法做到多工同時執行,那麼我們可以通過多個Agent同時執行不同的任務來模擬使用者訪問頁面。
3.2.1. 構建Agent
之前與開發溝通得到每個頁面最多可傳送的請求是6個,那麼我們準備6個Agent,分別為Agent1、Agent2、Agent3、Agent4、Agent5、Agent6
我們這裡使用Docker來啟動Agent、Agent對內開放埠: 5010、對外埠隨機,映象使用我們自建的: doddgu/crankagent:net5.0
並新建load.yml為之後壓測使用:
profiles:
crankAgent1:
jobs:
load:
endpoints:
- http://localhost:5010
crankAgent2:
jobs:
load:
endpoints:
- http://localhost:5011
crankAgent3:
jobs:
load:
endpoints:
- http://localhost:5012
crankAgent4:
jobs:
load:
endpoints:
- http://localhost:5013
crankAgent5:
jobs:
load:
endpoints:
- http://localhost:5014
crankAgent6:
jobs:
load:
endpoints:
- http://localhost:5015
load.yml 中記錄了所有的壓測機資訊,其資訊一般不做修改,我們可以作為公共的配置來使用無需每個專案都單獨維護一份新的
3.2.2. 構建壓測指令碼
在這裡我們選擇wrk2作為本次基準測試工具,選擇wrk2的原因是:
- 支援隨機引數
- 可支援設定恆定的吞吐量負載
- 具備wrk的所有功能
此時我們針對ProjectA專案新建配置:project.profiles.yml,作為本次壓測的環境配置來使用,其配置如下
imports:
- https://raw.githubusercontent.com/doddgu/crank/sample/samples/wrk2/common/load.profiles.yml # 這邊建議使用遠端load.profiles.yml地址。(如果輸入的是本地路徑、則需輸入與當前命令所在路徑的相對路徑)
profiles:
local: # 本地環境
variables:
serverAddress: localhost # 應用服務域
serverPort: 80 # 應用服務埠
connections: 256 # 每個執行緒處理時保持開啟的 HTTP 連線總數 N = 連線數/執行緒數
threads: 32 # 執行緒數
warmup: 3 # 預熱時間: 3s
duration: 180 # 測試時長: 3分鐘
rate: # 吞吐量引數(每秒總請求數)
project.profiles.yml中記錄了指定專案的各環境的配置,專案自己獨立維護即可
除了專案資訊、壓測機配置之外,我們還需要有地方維護我們壓測的介面資訊,這邊我的做法是將api獨立拆分出來,每個yml只配置一個介面的壓測資訊,至於為什麼不放到一塊,而要單獨拆分開呢?
這塊考慮到我們壓測的最小單元是API介面,如果把API介面獨立拆分開,那麼可以對單介面壓測,而如果我們需要場景壓測,也可以通過組合介面完成多介面同時壓測,並且一旦我們完成了某個介面的壓測編寫,後續不需要再改動這個配置,如果我們按照場景拆分成不同的yml,在yml中再根據定義不同的scenario來做,那麼後續場景新增加介面,還需要再更改這個場景的yml,並且scenario中的場景實際上也是根據介面維度區分的,目前crank並不能完成單個場景任務同時處理,基於以上原因,這邊我們新調整好的配置格式為:
新增load.benchmarks.yml
imports:
- https://raw.githubusercontent.com/doddgu/crank/sample/src/Microsoft.Crank.Jobs.Wrk2/wrk2.yml
- https://raw.githubusercontent.com/doddgu/crank/sample/samples/wrk2/common/project.profiles.yml
jobs:
server:
source:
repository: https://github.com/doddgu/crank
branchOrCommit: sample
project: samples/hello/hello.csproj
readyStateText: Application started.
scenarios:
api:
application: # 實際壓測專案時可移除此節點,此處是為模擬應用服務啟動
job: server
variables:
duration: 1
load:
job: wrk2
variables:
serverPath: /user/get
script: request.lua
duration: 1
profiles:
defaultParamLocal: # 本地環境的引數資訊
variables:
serverQueryString: ?id={1}
serverQueryParameter: 1||2 # 隨機請求/get?id=1、/get?id=2
按照此格式儲存,後續新增介面也可以快速複製,簡單修改即可快速完成壓測工作的編寫,這樣一來,如果我們希望對localhost:5000/user/get這個介面做壓測,僅需要在crank控制端輸入:
crank --config load.benchmarks.yml --scenario api --load.framework net5.0 --application.framework net5.0 --profile local --profile crankAgent1 --description "獲取使用者詳情" --profile defaultParamLocal
3.2.3. 構建批處理命令
但作為一個開發人員,總是希望事情能更簡單一點,每次輸入命令太麻煩了,所以就想到了通過批處理快速完成任務的傳送,最終的專案結構就變成了
benchmarks
├─ defaultTitle 介面名稱( Description )
└─ load.bat 最終執行的指令碼,其中指定了要指定的yml配置、場景、以及任務環境是.net 5.0
└─ load.benchmarks.yml yml配置
└─ load.local.bat 測試本地環境時要執行的指令碼、格式:load.{環境}.bat
└─ README.md 幫助文件
每次通過雙擊load.{環境}.bat就完成了對當前介面的壓力測試,然後就是等待結果輸出……
| application | |
| --------------------- | -------------- |
| CPU Usage (%) | 1 |
| Cores usage (%) | 10 |
| Working Set (MB) | 85 |
| Private Memory (MB) | 278 |
| Build Time (ms) | 3,469 |
| Start Time (ms) | 352 |
| Published Size (KB) | 93,323 |
| .NET Core SDK Version | 5.0.404 |
| ASP.NET Core Version | 5.0.13+55738ff |
| .NET Runtime Version | 5.0.13+b3afe99 |
| load | |
| --------------------- | -------------- |
| Build Time (ms) | 3,281 |
| Start Time (ms) | 0 |
| Published Size (KB) | 74,276 |
| .NET Core SDK Version | 5.0.404 |
| ASP.NET Core Version | 5.0.13+55738ff |
| .NET Runtime Version | 5.0.13+b3afe99 |
| First Request (ms) | 86 |
| Requests/sec | 2 |
| Requests | 2 |
| Mean latency (ms) | 2.68 |
| Max latency (ms) | 2.68 |
| Bad responses | 0 |
| Socket errors | 0 |
| Latency 50th (ms) | 2.68 |
| Latency 75th (ms) | 2.68 |
| Latency 90th (ms) | 2.68 |
| Latency 99th (ms) | 2.68 |
| Latency 99.9th (ms) | 2.68 |
| Latency 99.99th (ms) | 2.68 |
| Latency 99.999th (ms) | 2.68 |
3.2.4. 構建場景壓測批處理命令
通過上面的一番操作,我們已經可以很容易的對單介面進行壓測,但目前想模擬完成多介面同時壓測,還需要再改造一下,之前我們想到,crank目前只能完成單獨壓測任務,那是不是有多個Agent,每個Agent單獨壓測一個介面,並同時啟動多個Agent同時壓測是不是可以模擬出來場景壓測,那我通過批處理任務多點幾次不同的介面壓測不就可以了,基於以上考慮,又做了一個批處理指令碼,用於呼叫多個介面的壓測任務啟動,最後的結構如下所示:
Crank
├─ benchmarks 壓測指令碼
│ ├─ api 介面壓測指令碼
│ │ ├─ add
│ │ └─ get
│ ├─ scipts lua指令碼
│ │ ├─ common lua公共指令碼
│ │ │ ├─ oauth.lua 認證lua指令碼
│ │ │ ├─ util.lua lua工具類指令碼
│ │ ├─ request.lua 封裝請求lua指令碼
│ ├─ scripts.tar lua指令碼壓縮包
├─ common
│ ├─ load.profiles.yml agent 負載機配置
│ ├─ project.profiles.yml 專案配置
│ ├─ scripts.profiles crank 執行script配置,用於對輸出結果的二次處理
│ ├─ project.profiles.yml 專案配置
├─ scripts 場景壓測指令碼
│ ├─ 使用者.bat 使用者壓測
└─ env 環境配置,標記當前需要壓測的環境在哪個配置檔案中儲存
└─ env.local 本地環境,儲存本地環境的配置資訊
└─ README.md 幫助文件
4. 結尾
通過上面的操作我們已經完成了對單介面以及單場景的壓測,通過控制檯可以清晰的看到每個介面的壓測結果,我們只需要耐心等待壓測任務結束,並整理壓測結果資料,最後進行彙總我們的任務就完成了,但壓測結果的收集也是一個費事費力的工作,作為一個開發,是不想把時間花費到這些整理表格的事情上,那我們如何做可以把整理表格資料的工作節省下來讓我們可以歇會兒呢……
原始碼地址:https://github.com/doddgu/crank/tree/sample/samples/wrk2
參考連結:
開源地址
MASA.BuildingBlocks:https://github.com/masastack/MASA.BuildingBlocks
MASA.Contrib:https://github.com/masastack/MASA.Contrib
MASA.Utils:https://github.com/masastack/MASA.Utils
MASA.EShop:https://github.com/masalabs/MASA.EShop
MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor
如果你對我們的 MASA Framework 感興趣,無論是程式碼貢獻、使用、提 Issue,歡迎聯絡我們