HTTP 基準壓測工具 wrk 使用指南

前言

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 個鏈接,針對 / 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`,返回 truefalse
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
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://segmentfault.com/a/1190000038340980