前言
wrk是一個開源的、熱門的、現代的單機HTTP基準測試工具,目前在github開源平台累計了26.9k的star數目,足以可見wrk在Http基準測試領域的熱門程度。它結合了多線程設計和可擴充的事件通知系統,如epoll和kqueue,可以在有限的資源下并發出極緻的的負載請求。并且内置了一個可選的LuaJIT腳本執行引擎,可以處理複雜的HTTP請求生成、響應處理以及自定義壓測報告。
wrk項目位址:
https://github.com/wg/wrk安裝wrk
mac下安裝:
brew install wrk
其他平台參考:
https://github.com/wg/wrk/wiki基礎使用
wrk -t12 -c100 -d30s --latency http://localhost:8010/healthz
如上指令描述了采用12個線程,100個連結,針對
http://localhost:8010/healthz接口服務,持續壓測30s。wrk本身不是依賴線程數來模拟并發數的是以線程數量設定在核心數左右最好,線程數多了測試系統消耗大,可能帶來反效果。親測核心數一緻的線程數和兩倍核心數的線程數,前者壓出的QPS更高。結果如下:、、
Running 30s test @ http://localhost:8010/healthz (運作30s測試)
12 threads and 100 connections(12個線程100個連接配接)
Thread Stats Avg(均值) Stdev(标準內插補點) Max(最大值) +/- Stdev(正負标準內插補點)
Latency(延遲) 1.39ms 668.10us 23.95ms 90.34%
Req/Sec(每秒請求數) 5.44k 545.23 10.27k 76.47%
Latency Distribution(延遲直方圖)
50% 1.32ms (50%請求延遲在1.32ms内)
75% 1.49ms (75%請求延遲在1.49ms内)
90% 1.72ms (90%請求延遲在1.72ms内)
99% 4.77ms (99%請求延遲在4.77ms内)
1952790 requests in 30.08s, 271.90MB read (共1952790次請求,用時30s,傳輸了271.9M資料)
Requests/sec(每秒請求數): 64930.12
Transfer/sec(每秒傳輸資料): 9.04MB
wrk的結果相比ab測試結果來說,多了一個延時直方圖,有了這個直方圖,我們可以更清晰的看到延遲的分布情況。這也是部落客選擇wrk最重要的原因
常用指令說明
-c, --connections: 要保持打開的HTTP連接配接的總數,每個線程處理數N =連接配接/線程
-d, --duration: 測試持續時間, 如 2s, 2m, 2h
-t, --threads: 測試線程總數
-s, --script: 指定加載lua測試擴充腳本
-H, --header: 添加請求頭資訊, 如"User-Agent: wrk"
--latency: 列印延遲直方圖資訊
--timeout: 如果在此時間内沒有收到響應,則記錄逾時.
-開頭的指令為簡寫的,後面兩個列印延遲直方圖和逾時設定沒有簡寫的,隻能--開頭指定
高階用法,lua測試腳本
wrk内置了全局變量,全局方法,以及五個測試請求發起流程的方法,還有一個模拟延遲發送的方法,wrk是内置對象,在lua測試腳本的每個方法内都可以直接使用
- 全局變量
-- 全局的變量
wrk = {
scheme = "http",
host = "localhost",
port = nil,
method = "GET",
path = "/",
headers = {},
body = nil,
thread = userdata,
}
- 全局方法
-- 傳回請求字元串值,其中包含所傳遞的參數和來自wrk表的值。例如:傳回 http://www.kailing.pub
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)
- 請求過程方法
-- 請求前,對每個線程調用一次,并接收表示該線程的userdata對象。
function setup(thread)
thread.addr = "http://www.kailing.pub" -- 設定請求的位址
thread:get("name") -- 擷取全局變量的值
thread:set("name", "kl") -- 線上程的環境中設定全局變量的值
thread:stop() -- 停止線程
end
--初始化,每個線程執行一次
function init(args) --args為從指令行傳過來的額外參數
print(args)
end
--發起請求,每次請求執行一次,傳回包含HTTP請求的字元串。每次建構新請求的開銷都很大,在測試高性能伺服器時,
--一種解決方案是在init()中預先生成所有請求,并在request()中進行快速查找。
function request()
requests = requests + 1
return wrk.request()
end
--響應處理,每次請求執行一次
function response(status, headers, body)
responses = responses + 1
end
--請求完成,每次測試執行一次。done()函數接收一個包含結果資料的表和兩個統計資料對象,分别表示每個請求延遲和每個線程請求速率。
--持續時間和延遲是微秒值,速率是以每秒請求數來度量的。
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
整個腳本處理過程被分為準備階段、運作階段、完成階段。準備階段在目标IP位址被解析并且所有線程都已經初始化但還沒有啟動之後開始。運作階段從對init()的單個調用開始,然後對每個請求周期調用request()和response()。init()函數接收腳本的任何額外指令行參數,這些參數必須用“——”與wrk參數分隔開。
lua測試腳本案例分析
案例:我們線上有一個帶緩存場景的接口服務,根據appId的值的查詢結果緩存,是以,如果單純對指定的appId壓測,就變成了測試緩存系統的負載了,測試不出實際的服務性能,這個場景就需要測試工具發起每次請求的測試參數都是動态的。根據這個場景我們定制了如下的lua測試腳本:
-- 測試指令:wrk -t16 -c100 -d5s -sreview_digress_list.lua --latency htt://127.0.0.1:8081
wrk.method ="GET"
wrk.path = "/app/{appId}/review_digress_list"
function request()
-- 動态生成每個請求的url
local requestPath = string.gsub(wrk.path,"{appId}",math.random(1,10))
-- 傳回請求的完整字元串:http://127.0.0.1//app/666/review_digress_list
return wrk.format(nil, requestPath)
end