(1). Wrk是什么?
wrk是一款简单的HTTP压测工具,它能用很少的线程压出很大的并发量.原因是它使用了一些操作系统特定的高性能io机制,比如:select/epoll/kqueue等.
(2). wrk参数简介
lixin-macbook:~ lixin$ 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(压测机器开启多少个线程去做压测,建议是:CPU核心数)
-s, --script <S> Load Lua script file(lua脚本)
-H, --header <H> Add header to request(添加协议头信息)
--latency Print latency statistics
--timeout <T> Socket/request timeout(设置timeout)
-v, --version Print version details(打印详细信息)
Numeric arguments may include a SI unit (1k, 1M, 1G)
# s 秒
# m 分钟
# h 小时
Time arguments may include a time unit (2s, 2m, 2h)
(3). 压测(/hello请求)
lixin-macbook:~ lixin$ wrk -c200 -d30s -t8 http://localhost:8080/hello
Running 30s test @ http://localhost:8080/hello
# 8个线程,200个请求叠加
8 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 29.02ms 31.71ms 310.96ms 88.78%
Req/Sec 1.12k 322.68 3.28k 76.53%
Latency Distribution
50% 131.15ms
75% 205.00ms
90% 304.25ms
99% 544.24ms
# 30秒执行了265017个请求,读取了32.4M的数据
265017 requests in 30.08s, 32.40MB read
# 服务器每秒处理请求数(也就是:QPS)
Requests/sec: 8811.21
# 每少读取了1.08M的数据.
Transfer/sec: 1.08MB
(4). wrk生命周期
wrk中执行http请求的时候,调用lua分为3个阶段:setup/running/done,每个wrk线程中都有独立的脚本环境. 图片摘抄于:“舒润(博客园)”
wrk全局属性
wrk = {
scheme = "http",
host = "localhost",
port = nil,
method = "GET",
path = "/",
headers = {},
body = nil,
thread = <userdata>,
}
wrk的全局方法
-- 生成整个request的string,例如:返回
-- GET / HTTP/1.1
-- Host: tool.lu
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`,返回 true 或 false
function wrk.connect(addr)
Setup阶段(setup是在线程创建之后,启动之前)
function setup(thread)
-- thread提供了1个属性,3个方法
-- thread.addr 设置请求需要打到的ip
-- thread:get(name) 获取线程全局变量
-- thread:set(name, value) 设置线程全局变量
-- thread:stop() 终止线程
Running阶段
function init(args)
-- 每个线程仅调用1次,args 用于获取命令行中传入的参数, 例如 --env=pre
function delay()
-- 每个线程调用多次,发送下一个请求之前的延迟, 单位为ms
function request()
-- 每个线程调用多次,返回http请求
function response(status, headers, body)
-- 每个线程调用多次,返回http响应
Done阶段(可以用于自定义结果报表,整个过程中只执行一次)
function done(summary, latency, requests)
latency.min -- minimum value seen
latency.max -- maximum value seen
latency.mean -- average value seen
latency.stdev -- standard deviation
latency:percentile(99.0) -- 99th percentile value
latency(i) -- raw value and count
summary = {
duration = N, -- run duration in microseconds
requests = N, -- total completed requests
bytes = N, -- total bytes received
errors = {
connect = N, -- total socket connection errors
read = N, -- total socket read errors
write = N, -- total socket write errors
status = N, -- total HTTP status codes > 399
timeout = N -- total request timeouts
}
}
(5). wrk使用lua简单案例
wrk.method = "POST"
wrk.body = "{\"name\" : \"lixin\",\"age\" : 25 }" -- 直接写死,如果不需要请求数据的差异化
wrk.headers["Content-Type"] = "application/json"
response = function(status, headers, body)
if status ~= 200 then
print(body)
wrk.thread:stop()
end
end
(6). wrk使用lua复杂案例
读取本地文件,发送请求.
-- save-user-request.txt
wrk.method = "POST"
wrk.headers["Content-Type"] = "application/json"
-- 请求体内容
local bodys = {}
-- 读取json文件,保存结果到:bodys数组里.
-- init仅每个线程启动一次,不知道为什么不设计成:所有的线程共用一个.
-- 当然,这样设计的目的比较简单.
function init(args)
local i = 1;
for line in io.lines("users.json") do
bodys[i] = line;
i = i + 1;
end
end
-- 每个线程发起请求前之前,都执行一次.
-- #bodys : 代表获得数组的长度
local i = 1;
function request()
local body = wrk.format(nil, nil, nil, bodys[i % #bodys + 1])
i = i + 1
return body
end
response = function(status, headers, body)
if status ~= 200 then
print(body)
wrk.thread:stop()
end
end
-- users.json
{ "name" : "lixin-1", "age" : 20 }
{ "name" : "lixin-2", "age" : 21 }
{ "name" : "lixin-3", "age" : 22 }
{ "name" : "lixin-4", "age" : 23 }
{ "name" : "lixin-5", "age" : 24 }
{ "name" : "lixin-6", "age" : 25 }
{ "name" : "lixin-7", "age" : 26 }
{ "name" : "lixin-8", "age" : 27 }
{ "name" : "lixin-9", "age" : 28 }
// http://localhost:8080/save
@RestController
public class HelloController {
@PostMapping("/save")
public User save(@RequestBody User user) {
user.setAge(user.getAge() + 1);
return user;
}
}
class User implements Serializable {
private static final long serialVersionUID = 5114311963929663694L;
private String name;
private int age;
// ... setting/getting
}
(7). 总结
相比ab压测,wrk能应对比较复杂的场景,唯一不足:缺少编排功能.