一、wrk介紹
wrk 是一款針對 Http 協議的基准測試工具,它能夠在單機多核 CPU 的條件下,使用系統自帶的高性能 I/O 機制,如 epoll,kqueue 等,通過多線程和事件模式,對目標機器產生大量的負載。
二、 wrk 的優勢與劣勢
1、優勢:
- 輕量級性能測試工具;
- 安裝簡單(相對 Apache ab 來說);
- 學習曲線基本為零,幾分鍾就能學會咋用了;
- 基於系統自帶的高性能 I/O 機制,如 epoll, kqueue, 利用異步的事件驅動框架,通過很少的線程就可以壓出很大的並發量;
2、劣勢:
wrk 目前僅支持單機壓測,后續也不太可能支持多機器對目標機壓測,因為它本身的定位,並不是用來取代 JMeter, LoadRunner 等專業的測試工具,wrk 提供的功能,對我們后端開發人員來說,應付日常接口性能驗證還是比較友好的。
三、wrk 安裝
wrk 只能被安裝在類 Unix 系統上,所以我們需要一個 Linux 或者 MacOS 環境。
1、Ubuntu/Debian
依次執行如下命令:
sudo apt-get install build-essential libssl-dev -y
git clone https://github.com/wg/wrk.git wrk
cd wrk;make
sudo cp wrk /usr/local/bin
2、CentOS / RedHat / Fedora
依次執行如下命令:
sudo yum groupinstall 'Development Tools'
sudo yum install -y openssl-devel
git clone https://github.com/wg/wrk.git wrk
cd wrk;make
sudo cp wrk /usr/local/bin
3、MacOS 安裝
Mac 系統也可以通過源碼編譯安裝,但是更推薦使用 brew 的方式來安裝:
brew install wrk
四、使用步驟
1、簡單使用
wrk -t12 -c400 -d30s http://www.baidu.com
這條命令表示,利用 wrk 對 www.baidu.com 發起壓力測試,線程數為 12,模擬 400 個並發請求,持續 30 秒。
2、wrk 子命令參數說明
除了上面簡單示例中使用到的子命令參數,wrk 還有其他更豐富的功能,命令行中輸入 wrk --help, 可以看到支持以下子命令:
使用方法: wrk <選項> <被測HTTP服務的URL>
Options:
-c, --connections <N> 跟服務器建立並保持的TCP連接數量
-d, --duration <T> 壓測時間
-t, --threads <N> 使用多少個線程進行壓測
-s, --script <S> 指定Lua腳本路徑
-H, --header <H> 為每一個HTTP請求添加HTTP頭
--latency 在壓測結束后,打印延遲統計信息
--timeout <T> 超時時間
-v, --version 打印正在使用的wrk的詳細版本信息
注:
<N>代表數字參數,支持國際單位 (k, M, G)
<T>代表時間參數,支持時間單位 (s, m, h)
關於線程數不是設置的越大,壓測效果越好,一般來說,推薦設置成壓測機器 CPU 核心數的 2 倍到 4 倍就行了。線程設置過大會導致線程切換過於頻繁,效果降低,
3、測試報告
執行壓測命令:
wrk -t12 -c400 -d30s --latency http://www.baidu.com
執行上面的壓測命令,30 秒壓測過后,生成如下壓測報告:
Running 30s test @ http://www.baidu.com (壓測時間30s)
12 threads and 400 connections (共12個測試線程,400個連接)
Thread Stats Avg(平均值) Stdev(標准差) Max(最大值) +/- Stdev(正負一個標准差所占比例)
Latency 386.32ms 380.75ms 2.00s 86.66%
Req/Sec 17.06 13.91 252.00 87.89%
Latency Distribution (延遲分布)
50% 218.31ms
75% 520.60ms
90% 955.08ms
99% 1.93s
4922 requests in 30.06s, 73.86MB read (30.06s內處理了4922個請求,耗費流量73.86MB)
Socket errors: connect 0, read 0, write 0, timeout 311 (錯誤統計)
Requests/sec: 163.76 (QPS 163.76,即平均每秒處理請求數為163.76)
Transfer/sec: 2.46MB (平均每秒流量2.46MB)
注:
標准差,簡單來說,標准差是一組數據平均值分散程度的一種度量。一個較大的標准差,代表大部分數值和其平均值之間差異較大;一個較小的標准差,代表這些數值較接近平均值。標准差如果太大說明樣本本身離散程度比較高,有可能系統性能波動較大。
4、使用 Lua 腳本進行復雜測試
如果我想進行 POST 請求,而且每次的請求參數都不一樣,用來模擬用戶使用的實際場景,怎么測試?
對於這種需求,我們可以通過編寫 Lua 腳本的方式,在運行壓測命令時,通過參數 --script 來指定 Lua 腳本,來滿足個性化需求。
1)wrk 對 Lua 腳本的支持
wrk 支持在三個階段對壓測進行個性化,分別是啟動階段、運行階段和結束階段。每個測試線程,都擁有獨立的Lua 運行環境。
啟動階段:
function setup(thread)
在腳本文件中實現 setup 方法,wrk 就會在測試線程初始化后,但還沒有啟動的時候調用該方法。wrk會為每一個測試線程調用一次 setup 方法,並傳入代表測試線程的對象 thread 作為參數。setup 方法中可操作該 thread 對象,用於獲取信息、存儲信息、甚至關閉該線程。thread可做如下操作:
thread.addr - get or set the thread's server address thread:get(name) - get the value of a global in the thread's env thread:set(name, value) - set the value of a global in the thread's env thread:stop() - stop the thread
運行階段:
function init(args)
由測試線程調用,只會在進入運行階段時,調用一次。支持從啟動 wrk 的命令中,獲取命令行參數。
function delay()
在每次發送請求之前調用,如果需要定制延遲時間,可以在這個方法中設置。
function request()
用來生成請求, 每一次請求都會調用該方法,注意不要在該方法中做耗時的操作。
function response(status, headers, body)
在每次收到一個響應時被調用,為提升性能,如果沒有定義該方法,那么wrk不會解析 headers 和 body;
結束階段:
function done(summary, latency, requests)
在整個測試過程中只會被調用一次,我們可以從給定的參數中,獲取壓測結果,生成定制化的測試報告。
自定義 Lua 腳本中可訪問的變量以及方法:
變量wrk定義了一個 table 類型的全局變量,修改該 wrk 變量,會影響所有請求。
wrk = {
scheme = "http",
host = "localhost",
port = 8080,
method = "GET",
path = "/",
headers = {},
body = nil,
thread = <userdata>,
}
方法:
function wrk.format(method, path, headers, body)
wrk.format returns a HTTP request string containing the passed parameters merged with values from the wrk table.
根據參數和全局變量 wrk,生成一個 HTTP rquest 字符串。
function wrk.lookup(host, service)
wrk.lookup returns a table containing all known addresses for the host and service pair. This corresponds to the POSIX getaddrinfo() function.
給定 host 和 service(port/well known service name),返回所有可用的服務器地址信息。
function wrk.connect(addr)
wrk.connect returns true if the address can be connected to, otherwise it returns false. The address must be one returned from wrk.lookup().
測試給定的服務器地址信息是否可以成功創建連接
2)通過 Lua 腳本壓測示例
調用 POST 接口:
wrk.method = "POST"
wrk.body = "foo=bar&baz=quux"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
注意: wrk 是個全局變量,這里對其做了修改,使得所有請求都使用 POST 的方式,並指定了 body 和 Content-Type頭。
自定義每次請求的參數:
request = function()
uid = math.random(1, 10000000)
path = "/test?uid=" .. uid
return wrk.format(nil, path)
end
在 request 方法中,隨機生成 1~10000000 之間的 uid,並動態生成請求 URL.
每次請求前,延遲 10ms:
function delay()
return 10
end
請求的接口需要先進行認證,獲取 token 后,才能發起請求的處理方法:
token = nil
path = "/auth"
request = function()
return wrk.format("GET", path)
end
response = function(status, headers, body)
if not token and status == 200 then
token = headers["X-Token"]
path = "/test"
wrk.headers["X-Token"] = token
end
end
上面的腳本表示,在 token 為空的情況下,先請求 /auth 接口來認證,獲取 token, 拿到 token 以后,將 token 放置到請求頭中,再請求真正需要壓測的 /test 接口。
壓測支持 HTTP pipeline 的服務:
init = function(args)
local r = {}
r[1] = wrk.format(nil, "/?foo")
r[2] = wrk.format(nil, "/?bar")
r[3] = wrk.format(nil, "/?baz")
req = table.concat(r)
end
request = function()
return req
end
通過在 init 方法中將三個 HTTP請求拼接在一起,實現每次發送三個請求,以使用 HTTP pipeline。
附:參考文檔
https://www.cnblogs.com/quanxiaoha/p/10661650.html
