Linux压测神奇wrk介绍
简介
《wrk官方介绍》: wrk 是一种现代 HTTP 基准测试工具,能够在单个多核 CPU 上运行时产生大量负载。它将多线程设计与可扩展的事件通知系统(如 epoll 和 kqueue)相结合。 可选的 LuaJIT 脚本可以执行 HTTP 请求生成、响应处理和自定义报告。详细信息可在 SCRIPTING 中找到,几个示例位于 scripts/ 中。
wrk 定位
- 轻量级性能测试工具
- 仅支持 HTTP 协议
- 仅支持单机压测,多机器压测需要每个机器都手动执行一次 wrk 命令
- 不可取代 Jmeter、LR 等专业性能工具
基础用法
wrk --help
代码语言:shell复制[windealli@VM-52-29-centos workspace]$ wrk --help
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开启线程数
-s, --script <S> Load Lua script file 使用lua脚本
-H, --header <H> Add header to request
--latency Print latency statistics
--timeout <T> Socket/request timeout
-v, --version Print version details
Numeric arguments may include a SI unit (1k, 1M, 1G)
Time arguments may include a time unit (2s, 2m, 2h)
基础示例
下列示例,使用wrk命令对http://127.0.0.1:80/sayHello?power=10
进行压力测试。
wrk -t12 -c1000 -d30s http://127.0.0.1:80/sayHello?power=10
其中:
-t12
: 表示客户端启动了12个线程来进行压测。
-c1000
: 表示打开了1000个连接
-d30s
: 表示压测时间为30s
结果分析
代码语言:txt复制[windealli@VM-52-29-centos workspace]$ wrk -t12 -c1000 -d30s http://127.0.0.1:80
/sayHello?power=10
Running 30s test @ http://119.91.66.202/sayHello?power=10
12 threads and 1000 connections
Thread Stats Avg Stdev Max /- Stdev
Latency 172.14ms 317.41ms 1.99s 92.37%
Req/Sec 88.75 96.55 2.39k 91.91%
29616 requests in 30.10s, 36.59MB read
Socket errors: connect 0, read 0, write 0, timeout 1278
Requests/sec: 983.92
Transfer/sec: 1.22MB
[windealli@VM-52-29-centos workspace]$
结果说明:
在30s的压测里,共发起了29616个请求,读到了36.59M的数据。
平均每秒请求983.92,Requests/sec: 983.92
平均延时: 172.14ms, 最大延迟1.99s。
wrk与luaJIT
wrk支持编写基于luaJIT的脚本来实现复杂的基准测试。
官方文档wrk/SCRIPTING介绍其使用方法。
wrk的脚本能力可以归纳为以下几部分:
- 定义了全局的talbe:
wrk
- 提供了少量(三个)实现特定功能的API:
wrk.format()
,wrk.lookup()
,wrk.connect
- 约定了一些函数接口给用户实现,wrk会在约定的阶段调用这些函数接口。
全局table与API
在wrk中,开发接口,由一个全局的table
和一组全局函数组成:
全局table wrk
代码语言:txt复制 wrk = {
scheme = "http",
host = "localhost",
port = nil,
method = "GET",
path = "/",
headers = {},
body = nil,
thread = <userdata>,
}
function wrk.format(method, path, headers, body)
代码语言:lua复制-- wrk.format 根据入参构造一个http请求。入参会与全局table wrk合并,
-- @param method : http method, 如POST,GET
-- @param method : 请求的路径
-- @parma headers : 请求要携带的headers, 是个table
-- @param body : 请求包体
-- @return : 返回一个请求包,可以在running阶段的request()中选择使用
function wrk.format(method, path, headers, body)
function wrk.lookup(host, service)
代码语言:lua复制-- wrk.lookup : 根据参数host和service返回所有一直的地址信息, 对应POSIX的 getaddrinfo() 函数
function wrk.lookup(host, service)
function wrk.connect(addr)
代码语言:lua复制-- wrk.connect : 校验地址addr能否连接成功
-- @param addr : 要测试的目标地址,必须是wrk.lookup返回的地址
-- @return : 可以连接成功,返回true,否则返回false。
function wrk.connect(addr)
wrk 脚本运行的三个阶段
wrk支持在下面三个阶段运行luaJIT脚本
- setup: 启动阶段,(目的IP已解析,执行测试的线程已经初始化,但还未开始执行)
- running: 运行阶段,发送请求和接受应答。
- done: 各个测试线程已经结束,获得测试结果各项数据的table
每个阶段提供了相应的一些函数接口,wrk测试运行时会在相应的阶段调用这些接口。
setup
和done
阶段在独立的脚本运行环境中运行,不参与running
过程. 因此,不共享全局变量。
setup
阶段
setup阶段 从目的IP被解析完成开始,到测试线程初始化完成(但还未启动)结束。
在setup阶段,约定了一下接口
代码语言:lua复制-- setup 每次线程启动时调用。
-- @param thread 用于表示当前线程的userdata对象。
function setup(thread)
thread.addr -- get or set the thread's server address
thread:get(name) -- 从线程的环境变量中获取值
thread:set(name, value) -- 为线程设置环境变量,
thread:stop() -- 停止线程。
因为function setup(thread)
是在setup阶段调用,与running阶段在独立的脚本运行环境中。
不同的thread在此次共享全局变量。 我们可以在此处为各个编号。
running
阶段
running
阶段从线程的初始化init()
开始,然后就重复进行request()
和response()
循环
running
阶段约定了下列函数接口:
-- init 线程初始化
-- @param args 用于接收用户传入的命令行参数
function init(args)
-- delay 用于指定下一个请求发送的延迟时间
-- @return 返回延迟时间,单位是ms
function delay()
-- request 用于构造当前要发送的HTTP request, 通过request,可以达到每次请求内容都不一样的效果。 但是,由于构造请求有一定开销,测试是最好在init()阶段就(通过wrk.format())预生成请求包,然后在此处快速轮询/选择。
-- @return 要发送的HTTP request
function request()
-- response wrk收到请求应答时调用此方法,可以在此处对应答进行统计、分析等。
-- @param status : HTTP状态码
-- @param headers : 应答headers
-- @param body : 应答body
function response(status, headers, body)
done
阶段
当wrk各个thread测试完成(时间结束)是进入此阶段。
done
阶段约定了下面接口
-- done 测试完成是调用,根据根据入参进行此次基准测试的数据分析、展示
-- @params summary : 测试结果摘要(总请求数、各种错误指标等)
-- @params latency : 延迟相关的摘要信息。(最大延迟,最小延迟,平均延迟等)
-- @params requests : 测试结果摘要
function done(summary, latency, requests)
高级用法
发送一个POST请求
代码语言:lua复制-- post.lua
wrk.method="POST"
wrk.headers["Content-Type"] = "application/json"
wrk.body = "{"UserId":1001}"
代码语言:lua复制wrk -t2 -c1000 -d10s -s test.lua http://127.0.0.1:8080/sayHello
构造不同请求,并随机发送
测试脚本:
代码语言:lua复制local index = 0
function init(args)
-- 构造4个不同的请求
req = {}
req[0] = wrk.format("GET", "/?userId=1001", nil, nil)
req[1] = wrk.format("GET", "/?userId=1002", nil, nil)
end
function request()
index = index 1
return req[index%2]
end
测试命令:
代码语言:lua复制wrk -t2 -c1000 -d10s -s test.lua http://127.0.0.1:8080/
go编写的服务端程序:
代码语言:lua复制package main
import (
"fmt"
"net/http"
)
func handle(w http.ResponseWriter, r *http.Request) {
userId := r.URL.Query().Get("userId")
fmt.Printf("userId: %sn", userId)
w.Write([]byte("hello " userId))
// return
}
func main() {
http.HandleFunc("/", handle)
fmt.Println("http server start, :8080")
http.ListenAndServe(":8080", nil)
}
统计不用应答的个数
压测脚本
代码语言:lua复制local count_thread = 0
local threads = {}
function setup(thread)
thread:set("id", count_thread)
table.insert(threads, thread)
count_thread = count_thread 1
end
function init(args)
res_a = 0
res_b = 0
end
function response(status, headers, body)
if body == "a"
then
res_a = res_a 1
else
res_b = res_b 1
end
end
-- 统计应答为’a'和‘b'的数量
function done(summary, latency, requests)
for index, thread in ipairs(threads) do
local res_a = thread:get("res_a") -- 获取线程的res_a变量的值
local res_b = thread:get("res_b")
print("res_a: " .. res_a)
print("res_b: " .. res_b)
end
end
Go编写的服务端程序:
代码语言:go复制func handle(w http.ResponseWriter, r *http.Request) {
if time.Now().Unix()%5 == 0 {
w.Write([]byte("a"))
} else {
w.Write([]byte("b"))
}
}
func main() {
http.HandleFunc("/", handle)
fmt.Println("http server start, :8080")
http.ListenAndServe(":8080", nil)
}
查看测试结果,可以看到b
的响应数量是’a‘的4倍左右,符合预期。
➜ wrk git:(main) ✗ wrk -t2 -c1000 -d10s -s test.lua http://127.0.0.1:8080/
Running 10s test @ http://127.0.0.1:8080/
2 threads and 1000 connections
Thread Stats Avg Stdev Max /- Stdev
Latency 5.92ms 3.79ms 39.29ms 74.21%
Req/Sec 47.40k 13.73k 126.56k 82.63%
921607 requests in 10.05s, 102.83MB read
Socket errors: connect 0, read 1023, write 80, timeout 0
Requests/sec: 91722.38
Transfer/sec: 10.23MB
res_a: 92497
res_b: 372194
res_a: 93570
res_b: 363346
➜ wrk git:(main) ✗