wrk 介绍
在了解 wrk 之前,我对性能测试的了解一直局限在 ab(Apache Benchmark) 上,我印象中 ab 的功能比较简单,就是简单地压测给定的 API,然后看并发量是多少,这在一些全都是 get 的场景下倒是没什么问题,但是如果要涉及一些更复杂的测试就比较难搞了,所以我就了解了一下,然后看到别人安利了一下 wrk,所以就顺便记录一下关于 wrk 的东西。
功能概述
wrk 的基本功能其实也很 ab 很类似,可以简单地做一些 GET 的大量请求,然后算 QPS(Request Per Seconds) 和 RT(Response Time) 之类的,但是 wrk 不能直接执行 POST 请求(ab 可以)。所以,这里就需要进一步了解到 wrk 还有一个比较重要的功能:它支持通过 lua 脚本来做扩展,通过 lua 脚本,你可以做到:
- 执行一个自定义的 POST 请求
- 先调用一个登陆 API 拿到认证信息后,再给下一个测试 API 使用;
- 通过设置不同的 API 同时测试,更全面地测试并发能力;
- 调用完一个 API 后,等待一段时间再测试(这个 API 可能比较特殊)
- 自定义测试结果的展示效果
这些都是 lua 脚本的扩展功能,在这方便,会比 ab 会好用很多。有意思的一件事情是,如果你去找 wrk,你还会发现一个 wrk2 的东西,它其实是 wrk 的修改版本,作者自己加入了一个所谓的 constant throughput 的功能,其实就是不测试 QPS 了,而是保持一定的 QPS,然后看 server 端的响应速度如何,表现怎么样,所以,一般情况下,我是不需要 wrk2 的。
测试前准备
[root@liqiang.io]# setenforce 0 # 关闭 SELinux
[root@liqiang.io]# cat /proc/sys/fs/file-nr # 调高最大打开文件数
[root@liqiang.io]# ulimit -n 1024000 # 调高进程限制
[root@liqiang.io]# cat /etc/nginx/nginx.conf # 调高 nginx 限制
events { worker_connections 10240;}
基本功能演示
光说不练假把式,所以这里我只能演示一个简单的 GET 请求,其实就是看下 wrk 是如何执行这两个简单的操作的,至于 POST 请求,我需要在下一章节 扩展功能 中介绍:
一个 GET 请求
[root@liqiang.io]# wrk -t1 -c2 -d5s http://google.com
Running 5s test @ http://google.com
1 threads and 2 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 17.33ms 17.71ms 150.59ms 94.35%
Req/Sec 138.00 30.28 180.00 84.00%
692 requests in 5.03s, 356.81KB read
Requests/sec: 137.50
Transfer/sec: 70.90KB
这里我很抱歉拿 google.com 来作为一个演示的例子,但是这确实是工作的,这里先解析一下输入的参数分别是什么意思,这很重要:
-t1
: 表示开启一个线程来进行测试-c2
: 表示开启 2 个并发进行测试-d5s
: 表示执行测试 5s,从结果中可以看出,实际上执行时间是 5.03 秒,说明还是受控的- url: 这就是要进行测试的 url 了
扩展功能演示
lua 脚本执行逻辑
wrk 的 POST 请求不能直接通过命令行的参数执行,所以需要通过 lua 脚本的方式来运行,再看 lua 的编写和执行之前,先来了解一些 wrk 的 lua 的上下文概念:
- 在 wrk 中,每个线程的 lua 上下文是独立的,不会互相干扰,这对于编写 lua 脚本有意义
- 在 wrk 中,lua 脚本可以分为三部分,你可以只使用其中某一阶段的自定义脚本,也可以全都定义,这三个阶段分别是:
- 启动阶段(setup):每个线程启动时,会且整个生命周期只会执行一遍
- 运行阶段(start):执行测试的时候,lua 线程会调用里面的自定义函数
- 结束阶段(stop):测试完毕之后,lua 线程会调用里面的函数,一般用于控制输出内容格式
lua 脚本框架
看完理论部分,解析来看下,每个阶段对应的 lua 框架是什么样的,也就是说,wrk 规定的 lua 脚本应该怎么写。
启动阶段
lua 线程启动之后,就会调用这个阶段的自定义函数,这个节点只有一个自定义函数:
[root@liqiang.io]# cat setup.lua
local counter = 1
function setup(thread)
thread:set("id", counter)
table.insert(threads, thread)
counter = counter + 1
end
可以看到,在启动阶段,你可以修改 setup
函数的内容作为自定义的启动内容,里面你可以修改这个线程相关的信息,例如修改线程的变量,修改线程的 server 地址之类的,一般都不用修改,如果你想修改的话,这里有一些参数的函数可以供你使用:
函数(变量)名称 | 作用 |
---|---|
thread.addr | 变量:用于获取/修改服务器的地址 |
thread:get(name) | 函数:用于获取线程的全局变量 |
thread:set(name, value) | 函数:用于设置线程的全局变量,上面的例子演示了 |
thread:stop() | 函数:关闭线程,后面的两个阶段也不用执行啦 |
运行阶段
运行阶段是 wrk 的核心部分,在这一部分,你可以有多个函数可以定义,分别是:
函数名称 | 作用 |
---|---|
function init(args) | lua 线程进入运行阶段时,会执行一遍,后续就不执行了,你可以在这个阶段做一些请求的初始化之类的 |
function delay() | lua 线程每次发起请求之前都会调用一次这个 |
function request() | lua 线程会通过这个函数的响应作为请求进行发送 |
function response(status, headers, body) | lua 线程会将每次请求的响应作为参数调用这个函数 |
这里给一个示例每个阶段的代码是怎么写的:
[root@liqiang.io]# cat running.lua
function init(args)
requests = 0
responses = 0
local msg = "thread %d created"
print(msg:format(id))
end
function delay()
return math.random(10, 50)
end
function request()
requests = requests + 1
return wrk.request()
end
function response(status, headers, body)
responses = responses + 1
end
这就是一个简单的示例了,在示例里面,这里每个请求都延迟了 10-50 毫秒,然后在请求之前会记录一下请求数,请求成功之后也会记录一下请求的响应数量。
结束阶段
在结束之后,lua 允许你定义你自己的处理结果,在这里,你可以自定义你的输出格式,如果你不喜欢默认的输出格式的话,这里也只有一个你需要自己定义的函数:
[root@liqiang.io]# cat stop.lua
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
特别的例子
如果你只想修改一下 request 就好了,其他都不想自定义,那么你可以直接在 lua 里面写 request 里面的函数,不用 return,也不用写函数定义,下面个 POST 的示例就是这种情况。
POST 请求
如果按照我们上一小节介绍的 lua 框架来说,我们可以在 request() 里面定义请求的格式,将请求方式定义为 POST 就可以了,但是,最后我也提了一下,wrk 考虑到了这种场景的常见性,所以讲这个 feature 简化成你只需要写一段脚本就可以了,不用在定义 function 和 return 了,例如这样就可以了:
[root@liqiang.io]# cat post.lua
wrk.method = "POST"
wrk.body = "foo=bar&baz=quux"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
这就是一个 POST 的示例了,然后通过 wrk 加载这个 lua 脚本运行:
[root@liqiang.io]# wrk -c3 -d1s -t2 -s ./scripts/post.lua http://localhost:5000/api/v1/login
其他
lua 辅助函数
函数 | 解释 |
---|---|
wrk.format(method, path, headers, body) | 这个函数通常用于 request() 阶段返回 request 对象 |
done 的参数属性
done(summary, latency, requests)
里面记录了性能测试的很多数据,包含的字段如下:
对象 | 属性 | 解释 |
---|---|---|
Property | Description | |
—- | —- | |
summary | duration | run duration in microseconds |
summary | requests | total completed requests |
summary | bytes | total bytes received |
summary | errors.connect | total socket connection errors |
summary | errors.read | total socket read errors |
summary | errors.write | total socket write errors |
summary | errors.status | total HTTP status codes > 399 |
summary | errors.timeout | total request timeouts |
latency | min | minimum latency value reached during test |
latency | max | maximum latency value reached during test |
latency | mean | average latency value reached during test |
latency | stdev | latency standard deviation |
latency | percentile(99.0) | 99th percentile value |
latency[i] | raw latency data of request i | —— |
一个复杂的例子
[root@liqiang.io]# cat pipeline.lua
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