wrk压力测试

GitHub - wg/wrk: Modern HTTP benchmarking tool

wrk 是一个比较先进的 HTTP 压力测试工具,当在单个多核 CPU 上运行时,能够产生大量负载。它结合了多线程设计和可扩展的事件通知系统,例如 epoll 和 kqueue。

可选的 LuaJIT 脚本可以执行 HTTP 请求生成,响应处理和自定义报告。

1. wrk工具编译

1
2
3
4
# https://github.com/tsliwowicz/go-wrk
git clone https://github.com/wg/wrk.git
cd wrk
make

2. wrk参数说明

wrk -h

1
2
3
4
5
6
7
8
-c, --connections <N>  跟服务器建立并保持的TCP连接数量
-d, --duration <T> 压测时间
-t, --threads <N> 使用多少个线程进行压测,压测时,是有一个主线程来控制我们设置的n个子线程间调度
-s, --script <S> 指定Lua脚本路径
-H, --header <H> 为每一个HTTP请求添加HTTP头
--latency 在压测结束后,打印延迟统计信息
--timeout <T> 超时时间
-v, --version 打印正在使用的wrk的详细版本信

3. wrk使用

有关 ab 命令的使用,我们可以通过帮助命令进行查看。如下:

1
2
3
4
5
6
7
8
9
10
wrk -t8 -c400 -r10m http://localhost:8080/index.html

Making 10000000 requests to http://localhost:8080/index.html
8 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 439.75us 350.49us 7.60ms 92.88%
Req/Sec 61.13k 8.26k 72.00k 87.54%
10000088 requests in 19.87s, 3.42GB read
Requests/sec: 503396.23
Transfer/sec: 176.16MB

一般线程数不宜过多. 核数的2到4倍足够了. 多了反而因为线程切换过多造成效率降低. 因为 wrk 不是使用每个连接一个线程的模型, 而是通过异步网络 io 提升并发量. 所以网络通信不会阻塞线程执行. 这也是 wrk 可以用很少的线程模拟大量网路连接的原因. 而现在很多性能工具并没有采用这种方式, 而是采用提高线程数来实现高并发. 所以并发量一旦设的很高, 测试机自身压力就很大. 测试效果反而下降.

参数解释:

  • 12 threads and 100 connections:
    总共是12个线程,100个连接(不是一个线程对应一个连接)
  • latencyReq/Sec:
    代表单个线程的统计数据,latency代表延迟时间,Req/Sec代表单个线程每秒完成的请求数,他们都具有平均值, 标准偏差, 最大值, 正负一个标准差占比。一般我们来说我们主要关注平均值和最大值. 标准差如果太大说明样本本身离散程度比较高. 有可能系统性能波动很大.
  • 23725 requests in 30.05s, 347.47MB read
    在30秒之内总共有23725个请求,总共读取347.47MB的数据
  • Socket errors: connect 0, read 48, write 0, timeout 50
    总共有48个读错误,50个超时.
  • Requests/sec和Transfer/sec
    所有线程平均每秒钟完成了789.57个请求,每秒钟读取11.56MB数据量

如果想看看响应时间的分布,可以增加--latency:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
wrk -t12 -c100 -d30s --latency http://www.baidu.com

Running 30s test @ http://www.bing.com (压测时间30s)

8 threads and 200 connections (共8个测试线程,200个连接)

Thread Stats Avg Stdev Max +/- Stdev
(平均值) (标准差)(最大值)(正负一个标准差所占比例)
Latency 46.67ms 215.38ms 1.67s 95.59%
(延迟)
Req/Sec 7.91k 1.15k 10.26k 70.77%
(处理中的请求数)

Latency Distribution (延迟分布)
50% 2.93ms
75% 3.78ms
90% 4.73ms
99% 1.35s (99分位的延迟:%99的请求在1.35s以内)
1790465 requests in 30.01s, 684.08MB read (30.01秒内共处理完成了1790465个请求,读取了684.08MB数据)
Requests/sec: 59658.29 (平均每秒处理完成59658.29个请求)
Transfer/sec: 22.79MB (平均每秒读取数据22.79MB)

说明有50%的请求在2.93ms之内,90%在4.73ms之内。

高级用法

wrk可以结合lua来做,通过wrk提供的几个lua函数来对请求进行修改,结果输出、设置延迟等操作。下面来看看wrk提供的几个lua函数:

  • setup 函数
    这个函数在目标 IP 地址已经解析完, 并且所有 thread 已经生成, 但是还没有开始时被调用. 每个线程执行一次这个函数.
    可以通过thread:get(name), thread:set(name, value)设置线程级别的变量.
  • init 函数
    每次请求发送之前被调用.
    可以接受 wrk 命令行的额外参数. 通过 -- 指定.
  • delay函数
    这个函数返回一个数值, 在这次请求执行完以后延迟多长时间执行下一个请求. 可以对应 thinking time 的场景.
  • request函数
    通过这个函数可以每次请求之前修改本次请求的属性. 返回一个字符串. 这个函数要慎用, 会影响测试端性能.
  • response函数
    每次请求返回以后被调用. 可以根据响应内容做特殊处理, 比如遇到特殊响应停止执行测试, 或输出到控制台等等.
1
2
3
4
5
6
7
8
9
10
11
12
wrk.method = "POST"
wrk.headers["S-COOKIE2"]="a=2&b=Input&c=10.0&d=20191114***"
wrk.body = "recent_seven=20191127_32;20191128_111"
wrk.headers["Host"]="api.shouji.**.com"

function response(status, headers, body)
--将服务器返回状态码不是200的请求结果打印出来
if status ~= 200 then
print(body)
wrk.thread:stop()
end
end
  • done函数
    在所有请求执行完以后调用, 一般用于自定义统计结果.
1
2
3
4
5
6
7
done = function(summary, latency, requests)  
io.write("------------------------------\n")
for _, p in pairs({ 50, 90, 99, 99.999 }) do
n = latency:percentile(p)
io.write(string.format("%g%%,%d\n", p, n))
end
end

wrk官网提供的setup.lua实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
-- example script that demonstrates use of setup() to pass
-- data to and from the threads

local counter = 1
local threads = {}

function setup(thread)
thread:set("id", counter)
table.insert(threads, thread)
counter = counter + 1
end

function init(args)
requests = 0
responses = 0

local msg = "thread %d created"
print(msg:format(id))
end

function request()
requests = requests + 1
return wrk.request()
end
-- 发送json
--function request()
-- local headers = { }
-- headers['Content-Type'] = "application/json"
-- body = {
-- mobile={"1533899828"},
-- params={code=math.random(1000,9999)}
-- }
-- local cjson = require("cjson")
-- body_str = cjson.encode(body)
-- return wrk.format('POST', nil, headers, body_str)
--end

function response(status, headers, body)
responses = responses + 1
end

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

使用setup.lua:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@jerrik wrk]# wrk -t 4 -c 100 -d 20s --latency -s scripts/setup.lua https://www.baidu.com
thread 1 created
thread 2 created
thread 3 created
thread 4 created
Running 20s test @ https://www.baidu.com
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 251.75ms 336.19ms 2.00s 86.89%
Req/Sec 138.51 69.90 690.00 71.23%
Latency Distribution
50% 215.74ms
75% 401.87ms
90% 664.84ms
99% 1.54s
11021 requests in 20.02s, 162.82MB read
Socket errors: connect 0, read 3, write 0, timeout 50
Requests/sec: 550.62
Transfer/sec: 8.13MB
thread 1 made 2945 requests and got 2919 responses
thread 2 made 2831 requests and got 2807 responses
thread 3 made 2772 requests and got 2747 responses
thread 4 made 2573 requests and got 2548 responses
[root@jerrik wrk]#

将每个线程的请求数和响应数输出来了。其它更多使用可以参考github script目录下的lua脚本。

4.Post请求

编写lua脚本,填写post的数据, 如 post.lua

1
2
3
4
5
6
wrk.method = "POST"
wrk.body = '{"userId": "10001","coinType": "GT","type": "2","amount": "5.1"}'
wrk.headers["Content-Type"] = "application/json"
function request()
return wrk.format('POST', nil, nil, body)
end

执行wrk,开始压力测试:

1
wrk -t 16  -c 100 -d 30s --latency --timeout 5s -s post.lua http://localhost:8021/m/zh/order/new