前言:
以前一说到http的压力测试工具,多数人会说 ab、webbench、siege。 但这些工具不管是性能还是复杂度已经不能满足我们的需求了。 其实我从16年就开始接触wrk压力测试工具,先前使用的方法比较简单粗暴,那你可以想象的出来,用wrk只是为了更好的获取吞吐。
apache httpd 的ab有什么缺点么? 性能 !!!! wrk 使用了相当成熟的redis的ae事件库,避免了造轮子,事件库可以减少ab的大量线程的创建开销和调度开销. 另外他的httpparser也相当的优秀. 吹捧完了,总之,用wrk就对了。
为什么这次又突然谈起了wrk, 因这周要上线一个cdn的管理系统,我临时使用了python写了个测试脚本,发现压力上不去,作为压力脚本输出不给力,cpu吃的倒是挺多…. 这时候又突然想起了wrk这个暴力压测利器。 wrk有个更牛叉的特性就是支持自定义的lua脚本,像往常的压力测试工具,我们只是发起固定的请求,请求体是无法改变的。 但是wrk lua脚本不仅是可以做鉴权签名,可以改变请求体,模拟延迟,通过请求的返回值做些收尾。
该文章后续仍在不断更新中, 请移步到原文地址 http://xiaorui.cc/?p=5098
使用说明
wrk的安装我就不多说了,直接git拉下来,make就完事了。 wrk 已经不需要指定luajit了,默认会集成一份的。 show下wrk的用法,单纯从参数来说,确实没有ab那么丰富。
wrk Usage: wrk <options> <url> Options: -c, --connections <N> Connections to keep open -d, --duration <T> Duration of test -t, --threads <N> Number of threads to use -s, --script <S> Load Lua script file -H, --header <H> Add header to request --latency Print latency statistics --timeout <T> Socket/request timeout -v, --version Print version details Numeric arguments may include a SI unit (1k, 1M, 1G) Time arguments may include a time unit (2s, 2m, 2h)
wrk的生命周期
wrk 可以在lua脚本里添加下面的Hook函数,你可以想象成生命周期,每个生命周期做的事情都不一样, 但是生命周期是有时间顺序的。我们常用一般是 request 和 delay 周期.
setup 线程出事后支持会调用一次。 init 每次请求发送之前被调用。可以接受 wrk 命令行的额外参数。 delay 这个函数返回一个数值,在这次请求执行完以后延迟多长时间执行下一个请求,可以对应 thinking time 的场景。 request 通过这个函数可以每次请求之前修改本次请求体和Header,我们可以在这里写一些要压力测试的逻辑。 response 每次请求返回以后被调用,可以根据响应内容做特殊处理,比如遇到特殊响应停止执行测试,或输出到控制台等等。
wrk的使用案例
下面放一个我用到的构建 request 例子,针对一个接口压力测试,请求体的某个字段不能重复,要随机。
# xiaorui.cc counter = 0 charset = {} do -- [0-9a-zA-Z] for c = 48, 57 do table.insert(charset, string.char(c)) end for c = 65, 90 do table.insert(charset, string.char(c)) end for c = 97, 122 do table.insert(charset, string.char(c)) end end function randomString(length) if not length or length <= 0 then return '' end math.randomseed(os.clock()^5) return randomString(length - 1) .. charset[math.random(1, #charset)] end function init(args) local msg = "thread addr: %s" print(msg:format(wrk.thread.addr)) end request = function() counter = counter + 1 local task_id = string.format("%d-%s", os.time()*10000+math.random(1, 10000), randomString(10)) local data = [[{ "task_timeout": 30, "task_id": "%s", "task_body": { "url_list": [ { "subtask_id": "1000000022511092", "url": "http://zhihu.com/upic/2017/08/24/15/kk.webp", "force": true }, { "subtask_id": "1000000022511093", "url": "http://zhihu.com/upic/2017/08/24/15/zz.jpg", "force": true } ] }, "ip_list": [], "ip_list_flag": false, "task_type": "refresh", "retry_failed": false, "working_layer": "polymerization" }]] wrk.method = "POST" wrk.body = string.format(data, tostring(task_id)) wrk.headers["Content-Type"] = "application/json" -- return wrk.format("POST", "/v1/engine/task") return wrk.format() end
那么response hook怎么用? wrk每次通过request构建请求,当http返回报文时,wrk会回调 response hook方法。
# xiaorui.cc 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 = "/resource" wrk.headers["X-Token"] = token end end
中间遇到一个wrk的报错信息, incomplete request at 1:1 ,这个问题是由于request方法没有return wrk引起的。
wrk官方也提供了一些例子https://github.com/wg/wrk/tree/master/scripts, 但都很简短,能不能来点说明,敢不敢来点文档。
END