压力测试工具 Stress Testing Tools
测试工具比较
工具 | 特点 | 优点 | 缺点 |
---|---|---|---|
wrk | 高效的HTTP压力测试工具,适合简单场景 | 性能高,命令行简单易用 | 不支持复杂场景,无法执行JS代码 |
JMeter | 多协议支持,适合复杂测试场景 | 强大的GUI,丰富插件,支持分布式测试 | 资源消耗大,学习曲线陡峭 |
Gatling | 基于Scala,适合高并发测试 | 高性能,强大DSL,直观报告 | 学习难度高,对Scala不熟悉者不友好 |
k6 | 现代化工具,使用JavaScript编写测试脚本 | 轻量级,易集成到CI/CD管道 | 对复杂场景支持有限 |
Locust | 基于Python,用户行为模拟 | 易于编写Python脚本,支持分布式测试 | 高并发下性能可能受限 |
Siege | 简单的命令行工具,快速HTTP测试 | 简单易用,适合快速测试 | 功能基础,缺乏复杂场景支持 |
Artillery | 现代化工具,支持HTTP和WebSocket测试 | 易于维护,支持云端测试 | 社区和生态系统相对较小 |
BlazeMeter | 云端平台,兼容JMeter和Gatling | 支持大规模分布式测试,与JMeter兼容 | 大规模测试可能需要付费 |
Tsung | 分布式负载测试工具,多协议支持 | 高性能,支持大规模测试 | 配置复杂,文档较少 |
Wrk:CLI HTTP Testing
Wrk 是现代的 HTTP 压力测试命令行工具,可以在单机上运行并能够生成大量的请求。
- 它结合了多线程设计和可扩展的事件通知系统(比如 epoll 和 kqueue)。
- 可以使用
LuaJIT
脚本来生成 HTTP 请求,响应处理和自定义的报告。官方样例可见 link
基本使用
下面这个命令使用了 12 个线程,保持 400 个 HTTP 连接,测试时长 30 秒。
wrk -t12 -c400 -d30s http://127.0.0.1:8080/index.html
输出如下
Running 30s test @ http://127.0.0.1:8080/index.html
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 108.34ms 138.31ms 1.93s 92.09%
Req/Sec 217.35 105.66 515.00 65.71%
58791 requests in 30.09s, 124.30MB read
Socket errors: connect 155, read 299, write 0, timeout 471
Requests/sec: 1953.92
Transfer/sec: 4.13MB
详解(可跳过)
-
第一二行标明基本的参数,运行时间、线程数和连接数
-
随后展示了以线程为单位,请求的延时(Latency)和每秒请求数(Req/Sec)的数据统计
-
平均值(Avg),标准差(Stdev),最大值(Max),标准差范围内的百分比(+/- Stdev)
-
延时
- 平均延迟为 108.34ms,表示从发送请求到收到响应的平均时间是108.34毫秒。
- 标准差为 138.31ms
- 最大请求延时为 1.93s
- 在标准差延时内的请求占比 92.09%
-
每秒请求数
-
平均每秒请求数为 217.35,表示每个线程平均每秒处理217.35个请求。
- 所以总请求数为(
217.35 x 12 x 30
)
- 所以总请求数为(
-
标准差为 105.66
-
最大请求延时为 515
-
标准差百分比为 65.71%
-
-
在 30 秒内总共有 58791 个请求,在测试期间,从服务器读取的数据总量为 124.30MB。
-
-
Socket errors
:在测试过程中发生的 Socket 连接相关的错误统计。- connect 155:在尝试建立连接时发生了155次错误。这可能是由于服务器负载过高,导致无法建立新 的连接。
- read 299:在读取数据时发生了299次错误,可能是由于网络问题或服务器在发送数据时出现了异常。
- write 0:在写入数据时没有发生任何错误。
- timeout 471:出现了471次超时错误,表示客户端在等待服务器响应时超过了预期的时间。超时可能表明服务器在处理请求时遇到了瓶颈,响应时间过长。
-
Requests/sec:系统平均每秒处理了1953.92个请求。这个指标反映了系统的吞吐量,即在给定时间内可以处理的请求数量。
-
Transfer/sec:系统每秒传输的数据量为4.13MB。这表示在测试期间,系统每秒钟向客户端发送了大约4.13MB的数据量。
参数
- 连接数
- 延时
- 线程数
- LuaJIT 脚本
- 指定 HTTP Header
- 是否打印延时统计
- 是否记录超时请求
-c, --connections: total number of HTTP connections to keep open with
each thread handling N = connections/threads
-d, --duration: duration of the test, e.g. 2s, 2m, 2h
-t, --threads: total number of threads to use
-s, --script: LuaJIT script, see SCRIPTING
-H, --header: HTTP header to add to request, e.g. "User-Agent: wrk"
--latency: print detailed latency statistics
--timeout: record a timeout if a response is not received within
this amount of time.
脚本
生命周期
在 wrk
的 Lua 脚本中,生命周期可以分为几个主要的阶段,每个阶段对应特定的函数或操作:
- 初始化阶段:
- 在这个阶段,可以定义全局变量或执行一次性初始化操作。
- 通常用来设置请求的默认属性,如
wrk.method
、wrk.path
、wrk.headers
等。
- 请求生成阶段:
- 通过定义
request()
函数来生成每个请求的内容。 - 这个函数在每次请求时都会被调用,生成并返回一个请求字符串。
- 通过定义
- 响应处理阶段:
- 通过定义
response(status, headers, body)
函数来处理服务器返回的响应。 - 这个函数会在每次收到响应时被调用,用于分析响应内容、记录日志或执行其他操作。
- 通过定义
- 结束阶段:
- 这个阶段通常是脚本执行完所有请求后,可能执行一些清理操作或生成报告的逻辑。
- 通常在这个阶段,没有特定的内置函数会被自动调用,但可以在脚本末尾手动添加清理代码。
这些阶段构成了 wrk
Lua 脚本的基本生命周期,每个阶段的函数在特定的时间点被执行,帮助控制整个负载测试的流程。
注意事项
简单的请求修改不会影响性能,但如果你在脚本中加入了复杂的逻辑处理,每秒可以模拟的请求数就会减少。
-
简单修改(不会影响性能)修改HTTP方法、路径、添加头部信息或请求体
-- 修改HTTP方法为POST,设置路径,并添加一个自定义Header
wrk.method = "POST"
wrk.path = "/api/resource"
wrk.headers["Content-Type"] = "application/json"
wrk.body = '{"key": "value"}' -
复杂修改(可能影响性能)
-- 为每个请求动态生成路径和请求体,并使用 response() 函数处理响应。这个函数在每次请求时都会被调用,生成并返回一个请求字符串。
request = function()
local id = math.random(1, 1000)
wrk.path = "/api/resource/" .. id
wrk.body = '{"key": "' .. id .. '"}'
return wrk.format("POST", wrk.path, nil, wrk.body)
end
response = function(status, headers, body)
-- 处理响应,例如日志记录或统计分析
if status ~= 200 then
print("Non-200 response: " .. status)
end
end
wrk
内置的 Lua 脚本本身不支持线程间的状态共享。
- 在
wrk
的 Lua 脚本中,全局变量在每个线程中都是独立的,也就是说每个线程都会有自己独立的一份全局变量。 - 可以使用 wrk 的 init 函数来初始化每个线程,并确保 counter 的递增操作在线程之间是同步的。这通常需要使用一些外部机制(例如 Redis)来共享状态。
官方脚本样例
下面的样例可在github repo中找到
- 设置 HTTP POST 请求参数
- 按计数变更请求
- 在每次请求前延时 10~50 毫秒
- 在满足特定条件时停止一个线程
- 在
wrk
负载测试完成后输出延迟的关键百分位数,并以 CSV 格式展示 - 使用
wrk
负载测试工具模拟一个简单的身份验证流程,获取一个认证令牌(token),并将该令牌附加到所有后续请求的头部中 - 其他
- 多服务器场景下,给 thread 分配随机服务器地址,测试负载均衡
设置 HTTP POST 请求参数
-- example HTTP POST script which demonstrates setting the
-- HTTP method, body, and adding a header
wrk.method = "POST"
wrk.body = "foo=bar&baz=quux"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
按计数变更请求
-- example dynamic request script which demonstrates changing
-- the request path and a header for each request
-------------------------------------------------------------
-- NOTE: each wrk thread has an independent Lua scripting
-- context and thus there will be one counter per thread
counter = 0
request = function()
path = "/" .. counter
wrk.headers["X-Counter"] = counter
counter = counter + 1
return wrk.format(nil, path)
end
在每次请求前延时 10~50 毫秒
-- example script that demonstrates adding a random
-- 10-50ms delay before each request
function delay()
return math.random(10, 50)
end
在满足特定条件时停止一个线程,以及计数
-- example script that demonstrates use of thread:stop()
local counter = 1
function response()
if counter == 100 then
wrk.thread:stop() -- <==== stop thread while counter is euqal to 100
end
counter = counter + 1
end