关于压测
常见的系统开发过程,压测总是被置于整个周期的末尾,比如项目基本功能开发完成或者临近上线时才开始准备压测。这个时候往往累积了大量的“小问题”,导致压测进行的比较缓慢,如果遇到前期“一压就崩”的状态则更让人奔溃。按照我的个人经验,压测之所以被经常性的忽略,不外乎压测脚本编写麻烦,场景梳理费时费力,而且特别占用开始时间等因素。其实如果不考虑复杂的业务场景,可以使用wrk/ad等简单http压测工具对刚完成开发的接口进行针对性压测,可以帮助后端开发人员更早的发现性能上的问题。
wrk
wrk 是一个简单小巧的 http 性能测试工具,只有一个命令行,就能做很多基本的 http 性能测试。
wrk 本身是开源项目,代码在 github 上:https://github.com/wg/wrk
安装
wrk 具体的安装过程可以参考其 github上的wiki:https://github.com/wg/wrk/wiki。
本质上wrk只能运行在类Unix 的系统上,所以Win10其实要开启WSL(Windows SubLinux)才能正常安装使用。当然,官方wiki里都有提及,按照wiki中的提示操作即可。
一键压测
如果针对一个GET
请求的Http接口,wrk基本上可以一键完成,比如:
# 使用wrk创建12个线程
# 维持 200 http并发连接
# 对指定接口【GET www.baidu.com】进行持续30秒的压测
wrk -t12 -c200 -d30s https://www.baidu.com
wrk命令的详细选项如下:
-
-c, --connections
: http并发数(默认使用http1.1,会复用TCP链接) -
-d, --duration
: 持续压测时间, 比如:2s
,2m
,2h
-
-t, --threads
: 总线程数(建议为核心数的2倍即可)
--s, --script
: 指定执行Lua脚本(可以使用Lua脚本实现更高阶的功能) -
-H, --header
: 添加http header, 比如.-H 'Authorization: Bearer {token_value}'
来添加接口认证信息 -
--latency
: 在控制台打印出延迟统计情况 -
--timeout
: http超时时间
用过lua脚本处理更复杂的情况
用命令行其实已经可以覆盖很多简单的场景了。但是如果http接口稍微复杂些,就需要使用Lua脚本辅助。比如 接口为POST类型,body中有参数,响应需要写断言等等。
比如下面这个简单的lua脚本,修改了全局对象wrk的三个属性,method,body,headers,最终所有发出的http请求都会被影响。
wrk.method = "POST"
wrk.body = "foo=bar&baz=quux"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
Lua脚本执行的简单理解
想要编写更加复杂的Lua脚本,需要了解wrk提供的几个hook函数。
wrk 提供了几个 hook 函数,可以用 lua 来编写一些复杂场景下的测试:
- setup
这个函数在目标 IP 地址已经解析完,并且所有 thread 已经生成,但是还没有开始时被调用,每个线程执行一次这个函数。可以通过 thread:get(name), thread:set(name, value) 设置线程级别的变量。
- init
每次请求发送之前被调用。可以接受 wrk 命令行的额外参数,通过 – 指定。
- delay
这个函数返回一个数值,在这次请求执行完以后延迟多长时间执行下一个请求,可以对应 thinking time 的场景。
- request
通过这个函数可以每次请求之前修改本次请求的属性,返回一个字符串,这个函数要慎用, 会影响测试端性能。
- response
每次请求返回以后被调用,可以根据响应内容做特殊处理,比如遇到特殊响应停止执行测试,或输出到控制台等等。
这里提供一个我自己使用的简单lua脚本
-- 打印respose到控制台
response = function(status, headers, body)
-- 这里其实可以解析body,写断言,针对性打印比较好
print("status:", status)
print("body:", body)
end
-- 自定义分析压测结果
done = function(summary, latency, requests)
local durations=summary.duration / 1000000 -- 执行时间,单位是秒
local errors=summary.errors.status -- http status不是200,300开头的
local requests=summary.requests -- 总的请求数
local valid=requests-errors -- 有效请求数=总请求数-error请求数
io.write("Durations: "..string.format("%.2f",durations).."s".."\n")
io.write("Requests: "..summary.requests.."\n")
io.write("Avg RT: "..string.format("%.2f",latency.mean / 1000).."ms".."\n")
io.write("Max RT: "..(latency.max / 1000).."ms".."\n")
io.write("Min RT: "..(latency.min / 1000).."ms".."\n")
io.write("Error requests: "..errors.."\n")
io.write("Valid requests: "..valid.."\n")
io.write("QPS: "..string.format("%.2f",valid / durations).."\n")
io.write("--------------------------\n")
end
wrk源码中提供了一些常见的Lua脚本demo,可以参考:https://github.com/wg/wrk/tree/master/scripts
建议
其实这个建议就是官方的readme,简单翻一下:
如果仅仅更改HTTP方法,修改路径,添加标头或正文的Lua脚本不会对压测的性能产生影响。但是针对每个请求的操作(特别是构建新的HTTP请求)以及使用 response()
函数 必然会减少可以生成的负载量。
A user script that only changes the HTTP method, path, adds headers or a body, will have no performance impact. Per-request actions, particularly building a new HTTP request, and use of response() will necessarily reduce the amount of load that can be generated.
小技巧
-H
参数和curl
的-H
参数含义一致,所以在实际使用中可以先使用curl
确认编写的命令是否正确,或者也可以使用postman来确认,然后使用postman生成curl
的请求命令即可。