關(guān)于壓測
常見的系統(tǒng)開發(fā)過程,壓測總是被置于整個周期的末尾,比如項目基本功能開發(fā)完成或者臨近上線時才開始準備壓測。這個時候往往累積了大量的“小問題”,導(dǎo)致壓測進行的比較緩慢,如果遇到前期“一壓就崩”的狀態(tài)則更讓人奔潰。按照我的個人經(jīng)驗,壓測之所以被經(jīng)常性的忽略,不外乎壓測腳本編寫麻煩,場景梳理費時費力,而且特別占用開始時間等因素。其實如果不考慮復(fù)雜的業(yè)務(wù)場景,可以使用wrk/ad等簡單http壓測工具對剛完成開發(fā)的接口進行針對性壓測,可以幫助后端開發(fā)人員更早的發(fā)現(xiàn)性能上的問題。
wrk
wrk 是一個簡單小巧的 http 性能測試工具,只有一個命令行,就能做很多基本的 http 性能測試。
wrk 本身是開源項目,代碼在 github 上:https://github.com/wg/wrk
安裝
wrk 具體的安裝過程可以參考其 github上的wiki:https://github.com/wg/wrk/wiki。
本質(zhì)上wrk只能運行在類Unix 的系統(tǒng)上,所以Win10其實要開啟WSL(Windows SubLinux)才能正常安裝使用。當然,官方wiki里都有提及,按照wiki中的提示操作即可。
一鍵壓測
如果針對一個GET請求的Http接口,wrk基本上可以一鍵完成,比如:
# 使用wrk創(chuàng)建12個線程
# 維持 200 http并發(fā)連接
# 對指定接口【GET www.baidu.com】進行持續(xù)30秒的壓測
wrk -t12 -c200 -d30s https://www.baidu.com

wrk命令的詳細選項如下:
-
-c, --connections: http并發(fā)數(shù)(默認使用http1.1,會復(fù)用TCP鏈接) -
-d, --duration: 持續(xù)壓測時間, 比如:2s,2m,2h -
-t, --threads: 總線程數(shù)(建議為核心數(shù)的2倍即可)
--s, --script: 指定執(zhí)行Lua腳本(可以使用Lua腳本實現(xiàn)更高階的功能) -
-H, --header: 添加http header, 比如.-H 'Authorization: Bearer {token_value}'來添加接口認證信息 -
--latency: 在控制臺打印出延遲統(tǒng)計情況 -
--timeout: http超時時間
用過lua腳本處理更復(fù)雜的情況
用命令行其實已經(jīng)可以覆蓋很多簡單的場景了。但是如果http接口稍微復(fù)雜些,就需要使用Lua腳本輔助。比如 接口為POST類型,body中有參數(shù),響應(yīng)需要寫斷言等等。
比如下面這個簡單的lua腳本,修改了全局對象wrk的三個屬性,method,body,headers,最終所有發(fā)出的http請求都會被影響。
wrk.method = "POST"
wrk.body = "foo=bar&baz=quux"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
Lua腳本執(zhí)行的簡單理解
想要編寫更加復(fù)雜的Lua腳本,需要了解wrk提供的幾個hook函數(shù)。

wrk 提供了幾個 hook 函數(shù),可以用 lua 來編寫一些復(fù)雜場景下的測試:
- setup
這個函數(shù)在目標 IP 地址已經(jīng)解析完,并且所有 thread 已經(jīng)生成,但是還沒有開始時被調(diào)用,每個線程執(zhí)行一次這個函數(shù)??梢酝ㄟ^ thread:get(name), thread:set(name, value) 設(shè)置線程級別的變量。
- init
每次請求發(fā)送之前被調(diào)用??梢越邮?wrk 命令行的額外參數(shù),通過 – 指定。
- delay
這個函數(shù)返回一個數(shù)值,在這次請求執(zhí)行完以后延遲多長時間執(zhí)行下一個請求,可以對應(yīng) thinking time 的場景。
- request
通過這個函數(shù)可以每次請求之前修改本次請求的屬性,返回一個字符串,這個函數(shù)要慎用, 會影響測試端性能。
- response
每次請求返回以后被調(diào)用,可以根據(jù)響應(yīng)內(nèi)容做特殊處理,比如遇到特殊響應(yīng)停止執(zhí)行測試,或輸出到控制臺等等。
這里提供一個我自己使用的簡單lua腳本
-- 打印respose到控制臺
response = function(status, headers, body)
-- 這里其實可以解析body,寫斷言,針對性打印比較好
print("status:", status)
print("body:", body)
end
-- 自定義分析壓測結(jié)果
done = function(summary, latency, requests)
local durations=summary.duration / 1000000 -- 執(zhí)行時間,單位是秒
local errors=summary.errors.status -- http status不是200,300開頭的
local requests=summary.requests -- 總的請求數(shù)
local valid=requests-errors -- 有效請求數(shù)=總請求數(shù)-error請求數(shù)
io.write("Durations: "..string.format("%.2f",durations).."s".."\n")
io.write("Requests: "..summary.requests.."\n")
io.write("Avg RT: "..string.format("%.2f",latency.mean / 1000).."ms".."\n")
io.write("Max RT: "..(latency.max / 1000).."ms".."\n")
io.write("Min RT: "..(latency.min / 1000).."ms".."\n")
io.write("Error requests: "..errors.."\n")
io.write("Valid requests: "..valid.."\n")
io.write("QPS: "..string.format("%.2f",valid / durations).."\n")
io.write("--------------------------\n")
end
wrk源碼中提供了一些常見的Lua腳本demo,可以參考:https://github.com/wg/wrk/tree/master/scripts
建議
其實這個建議就是官方的readme,簡單翻一下:
如果僅僅更改HTTP方法,修改路徑,添加標頭或正文的Lua腳本不會對壓測的性能產(chǎn)生影響。但是針對每個請求的操作(特別是構(gòu)建新的HTTP請求)以及使用 response()函數(shù) 必然會減少可以生成的負載量。
A user script that only changes the HTTP method, path, adds headers or a body, will have no performance impact. Per-request actions, particularly building a new HTTP request, and use of response() will necessarily reduce the amount of load that can be generated.
小技巧
-H參數(shù)和curl的-H參數(shù)含義一致,所以在實際使用中可以先使用curl確認編寫的命令是否正確,或者也可以使用postman來確認,然后使用postman生成curl的請求命令即可。
