Linux压测神奇wrk介绍

2023-03-20 09:13:37 浏览数 (2)

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进行压力测试。

代码语言:shell复制
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的脚本能力可以归纳为以下几部分:

  1. 定义了全局的talbe:wrk
  2. 提供了少量(三个)实现特定功能的API:wrk.format(),wrk.lookup(),wrk.connect
  3. 约定了一些函数接口给用户实现,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测试运行时会在相应的阶段调用这些接口。

setupdone 阶段在独立的脚本运行环境中运行,不参与 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阶段约定了下列函数接口:

代码语言:lua复制
-- 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阶段约定了下面接口

代码语言:lua复制
-- 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倍左右,符合预期。

代码语言:shell复制
➜  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) ✗ 

0 人点赞