0. 簡要介紹
WRK 是一款輕量且易用的 HTTP 壓力測試工具,通過該工具我們可以方便地對我們所開發的 WebAPI 項目進行壓力測試,並且針對測試的情況返回結果。
PS:Wrk 並不能針對測試的結果生成動態的圖表,如果有這種需要,可以嘗試使用另一款工具 Vegeta。該項目使用的 Golang 進行編寫,其 GitHub 地址為:https://github.com/tsenart/vegeta
下面的內容就是一個標准的測試結果信息:
# 針對 127.0.0.1:8080 進行壓力測試
wrk -t12 -c400 -d30s http://127.0.0.1:8080/index.html
# 這里是測試結果
Running 30s test @ http://127.0.0.1:8080/index.html
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 635.91us 0.89ms 12.92ms 93.69%
Req/Sec 56.20k 8.07k 62.00k 86.54%
22464657 requests in 30.00s, 17.76GB read
Requests/sec: 748868.53
Transfer/sec: 606.33MB
1. 安裝
關於 OS X 與 Windows 的安裝可以參考 Wrk 官方 WIKI 進行操作,本文主要講解一下 CentOS 7.x 下如果進行編譯。
sudo yum groupinstall 'Development Tools'
sudo yum install -y openssl-devel git
git clone https://github.com/wg/wrk.git wrk
cd wrk
make
編譯之后,你會得到如下結果:
可以看到生成了一個 wrk 的可執行文件,你可以將其添加到環境變量的 PATH 當中,這里就不再贅述,我們等會兒使用的時候直接 ./wrk 使用。
2. 命令說明
./wrk -H "Authorization: Bearer TokenValue" -t 2 -c 50 -d 10s --latency --timeout 1s "http://"
上面的命令就是一個典型的壓力測試命令,關於參數的含義請看下表。
執行命令時的參數 | 含義 | 示例 |
---|---|---|
-c | 與 HTTP 保持連接的連接數,最終每個線程能夠處理的為 連接數/線程數。 | -c 50 |
-d | 指定壓力測試的時間有多長。 | -d 10s,其他單位有 2s,2m,2h 如果不帶單位的話,默認為秒。 |
-t | 壓力測試時所使用的線程數目,最好為你 CPU 核心的數量。 | -t 4 |
-s | 指定要執行的 Lua 腳本 | -s ./post.lua |
-H | 執行請求的時候所附帶的 Header 組。 | -H "User-Agent: wrk" |
--latency | 打印詳細的統計信息。 | --latency |
--timeout | 每次請求所返回響應體的時間,如果超過了配置的時間,則視為請求超時。 | --timeout 1s |
3. 開始壓力測試
執行了上述代碼之后我們可以看到很直觀的信息,第一個就是 20s 的時間內完成了 2887 次請求,一共接受到了 2.46MB 的數據。在 Socket errors 里面我們可以看到有 35 個請求產生了超時的情況,每秒執行的請求大概為 144.20 個,每秒的數據傳輸大概為 125.75 KB。
除此之外,還說明了平均每次請求所消耗的時間為 338.44 ms,最極端的情況為 994.27ms。
4. LUA 腳本
在第三節我們可以看到一些標准的 GET 請求我們可以直接通過指定命令來進行測試,即便該接口有授權驗證,我們可以通過 -H
參數來指定 Authorization
頭來實現權限驗證。
但是針對一些復雜的情況,我們就需要編寫 LUA 腳本來實現壓力測試了。
官方編寫了很多的 LUA 腳本 DEMO ,存放在 GitHub 上面,其地址為:https://github.com/wg/wrk/tree/master/scripts。
這里我們以實現 POST 請求為例:
wrk.method = "POST"
wrk.body = '{"username":"admin","password":"123qwe","rememberClient":true}'
wrk.headers["Content-Type"] = "application/json"
這里我們的接口地址更改了一下,改變成了 Login 接口,該接口需要傳入用戶名與密碼,並且其 Method 為 POST。
將上述 LUA 腳本保存為 post.lua 文件,然后通過 -s
參數指定 LUA 腳本的路徑並執行。
5. LUA 腳本相關詳解
WRK 中執行 HTTP 請求的時候,調用 Lua 分為 3 個階段,setup
、running
、done
,每個 WRK 線程中都有獨立的腳本環境。
5.1 WRK 的全局屬性
wrk = {
scheme = "http",
host = "localhost",
port = nil,
method = "GET",
path = "/",
headers = {},
body = nil,
thread = <userdata>,
}
5.2 WRK 的全局方法
-- 生成整個request的string,例如:返回
-- GET / HTTP/1.1
-- Host: tool.lu
function wrk.format(method, path, headers, body)
-- 獲取域名的IP和端口,返回table,例如:返回 `{127.0.0.1:80}`
function wrk.lookup(host, service)
-- 判斷addr是否能連接,例如:`127.0.0.1:80`,返回 true 或 false
function wrk.connect(addr)
5.3 Setup 階段
setup()
方法是在線程創建之后,啟動之前。
function setup(thread)
-- thread提供了1個屬性,3個方法
-- thread.addr 設置請求需要打到的ip
-- thread:get(name) 獲取線程全局變量
-- thread:set(name, value) 設置線程全局變量
-- thread:stop() 終止線程
5.4 Running 階段
function init(args)
-- 每個線程僅調用1次,args 用於獲取命令行中傳入的參數, 例如 --env=pre
function delay()
-- 每個線程調用多次,發送下一個請求之前的延遲, 單位為ms
function request()
-- 每個線程調用多次,返回http請求
function response(status, headers, body)
-- 每個線程調用多次,返回http響應
5.5 Done 階段
可以用於自定義結果報表,整個過程中只執行一次。
function done(summary, latency, requests)
latency.min -- minimum value seen
latency.max -- maximum value seen
latency.mean -- average value seen
latency.stdev -- standard deviation
latency:percentile(99.0) -- 99th percentile value
latency(i) -- raw value and count
summary = {
duration = N, -- run duration in microseconds
requests = N, -- total completed requests
bytes = N, -- total bytes received
errors = {
connect = N, -- total socket connection errors
read = N, -- total socket read errors
write = N, -- total socket write errors
status = N, -- total HTTP status codes > 399
timeout = N -- total request timeouts
}
}
而官方的 setup.lua 腳本則是重載這些方法並使用的一個 DEMO:
-- example script that demonstrates use of setup() to pass
-- data to and from the threads
local counter = 1
local threads = {}
function setup(thread)
thread:set("id", counter)
table.insert(threads, thread)
counter = counter + 1
end
function init(args)
requests = 0
responses = 0
local msg = "thread %d created"
print(msg:format(id))
end
function request()
requests = requests + 1
return wrk.request()
end
function response(status, headers, body)
responses = responses + 1
end
function done(summary, latency, requests)
for index, thread in ipairs(threads) do
local id = thread:get("id")
local requests = thread:get("requests")
local responses = thread:get("responses")
local msg = "thread %d made %d requests and got %d responses"
print(msg:format(id, requests, responses))
end
end
6. 參考資料
wrk中的lua腳本:https://type.so/linux/lua-script-in-wrk.html
http 性能測試 wrk使用教程:https://juejin.im/post/5a59e74f5188257353008fea