web版本 开源压测工具_Web服务压测神器wrk

wrk是一款开源的高性能http压测工具(也支持https),很是小巧,能够执行文件只有3M(其中主要是luajit和openssl占用绝大多数空间),别看核心代码3-5年没更新了,但依旧很是好用。虽然很早以前我就知道有这么个工具了,当时学习这个工具的时候我还拿它压测了咱们的我的网站xindoo.me,发现mysql性能不行后加了wp-cache,经过cache把我网站的承载能力提高了10多倍。但当时以前简单使用它的初级功能,最近工做中刚好有个http服务须要压测,而后就拿wrk作了。此次使用了wrk lua高级功能实现了压测,咱们找到了咱们服务的瓶颈,同时也被wrk的超高性能所震惊。

如上图,我用单机(40 cores)压90台机器的集群,压到了31w的QPS,最后压不上去不是由于这台机器抗不住了,而是由于咱们服务扛不住了。一个有复杂业务逻辑的服务和一个毫无逻辑的压测相比有失公允,但在压测过程当中我也干垮了4台机器的nginx集群(这里nginx也只是个方向代理而已),这足见wrk性能之高。依赖lua脚本,wrk也能够完成复杂http请求的压测,接下来跟我一块儿了解下wrk的具体使用吧。mysql

wrk的一切内容都在githubhttps://github.com/wg/wrk上,不像其余各类流行的工具包包同样,它并无提供各个平台的可执行包,只有在mac上能够经过brew安装(应该也不是做者提供的)。好在编译wrk并不难,也不须要什么特殊的配置,git clone https://github.com/wg/wrk.git 或从github上直接下载zip包,进入项目目录后直接执行make,你就能够获得一个可执行文件wrk 。nginx

Options:

-c, --connections Connections to keep open # 指定创建多少个网络连接,全部线程复用这些连接

-d, --duration Duration of test # 指定总共起多少个线程

-t, --threads Number of threads to use # 压测持续多长时间

-s, --script Load Lua script file # 指定lua脚本文件,后文会详细介绍

-H, --header Add header to request # 指定http请求的header头

--latency Print latency statistics

--timeout Socket/request timeout

-v, --version Print version details # 输出版本号,经我测试其实是用不了的

wrk这个命令提供的参数也很少,运用这些参数能够一行命令完成一个简单http请求的压测,咱们以国民检测网络状况最经常使用的一个网站为例。git

> ./wrk https://www.baidu.com -c100 -t10 -d100s

Running 20s test @ https://www.baidu.com

10 threads and 100 connections

Thread Stats Avg Stdev Max +/- Stdev

Latency 145.48ms 91.46ms 1.24s 93.71%

Req/Sec 71.11 16.91 144.00 66.95%

14161 requests in 20.09s, 211.91MB read

Socket errors: connect 0, read 137, write 0, timeout 0

Requests/sec: 705.00

Transfer/sec: 10.55MB

经过一行shell命令就能够垂手可得完成对百度首页的压测,但若是你须要压一些复杂的http请求时,指定这些参数明显作不到,这时候就须要wrk的高级功能,经过-s指定lua脚本。 固然lua脚本也不是随便写了就能用的,须要按wrk的规范去写wrk才能正常调用。github

wrk封装了一个http请求的结构,他是经过wrk这个结构体中的内容去完成一次http请求的,因此你想让http请求不一样只须要修改这里面的内容便可,wrk提供了让你修改内容的方法。注意:wrk每一个线程都是单独的lua运行环境,互不干扰,没有交集。若是你想在多线程共享一些数据的话,你能够用table这个全局变量来共享。web

wrk = {

scheme = "http",

host = "localhost",

port = nil,

method = "GET",

path = "/",

headers = {},

body = nil,

thread = ,

}

除了上述结构体外,wrk容许你重写有些给的的function来实现你请求的自定义,如下是其方法名和调用时机。redis

global setup -- 线程启动前调用一次

global init -- 线程启动后调用一次

global delay -- 每次发起一个请求都会调用

global request -- 每发起一个请求前都会调用

global response -- 获取到请求响应结果后调用

global done -- 压测结束后会调用一次

每一个方法都是可选的, 若是你想重定义某个阶段的行为,你能够选择重写该方法,具体方法介绍以下。sql

setup

function setup(thread)是有参数传入的,传入的内容就是当前的线程,setup是在ip地址解析后而且全部线程初始化后,但没用启动前执行的,因此这个时候你能够对thread的构造作一些自定义。shell

thread.addr - 设置当前线程压测的ip,能够指定线程只压测某个ip

thread:get(key) - 读取线程中某个key对应的值,后面能够用key-value执行不一样的逻辑

thread:set(key, value) - 在线程环境中设置一个KV

thread:stop() - 停掉线程,只能在线程还在运行的状况下调用

init

function init(args)是在线程启动后调用,这里是能够传参数的,在启动命令后加-- arg1 arg2,你就能够在init里经过args[1], args[2]获取到arg1和arg2,举例以下。网络

> ./wrk https://www.baidu.com -c100 -t10 -d100s -- 10 20

function init(args)

print(args[1]) -- 输出10

print(args[2]) -- 输出20

end

因此这里能够经过这种方式定义更多的自定义参数,而后经过init(args)作解析,后续能够实现多的功能。多线程

delay

function delay()就很简单了,它是为了让你去控制请求发送的之间间隔,若是你想隔10ms发送一次请求,直接return 10就好了,经过delay()能够实现qps大小的控制。

request()

function request()主要功能是为了定制每次请求的参数数据,若是你想构造一些复杂的请求,request()是不得不改的,你能够再request()中修改上文wrk 结构体中的全部值,基本上最长改动的就是wrk.header, wrk.path, wrk.body。这里须要注意,request()是要求有返回值的,其返回值是wrk.format(method, path, headers, body),wrk.format会将这些参数构形成一个http请求可用的请求数据。

response

function response(status, headers, body)是在每次wrk收到http请求响应后调用,wrk会将请求响应中的http status、headers和body做为参数传递进来,你能够经过这些参数信息作响应统计、调整压测流量、甚至中止压测……等比较自动化的操做。

done

function done(summary, latency, requests)是在压测结束后wrk会调用一次,即使有多个线程也只调用一次。wrk会将压测过程当中的统计信息经过参数传递给你,你能够挑其中有用的部分输出。也能够输出你在response()中自行统计的内容。

wrk已经为你提供了如下的统计信息:

latency.min -- 最小延迟

latency.max -- 最大延迟

latency.mean -- 平均延迟

latency.stdev -- 延迟的标准差

latency:percentile(99.0) -- 99分位的延迟

latency(i) -- raw value and count

summary = {

duration = N, -- 运行的时间ms

requests = N, -- 总请求数

bytes = N, -- 总过收到的字节数

errors = {

connect = N, -- 连接错误数

read = N, -- socket数据读取出错数量

write = N, -- socket数据写入出错数量

status = N, -- http code 大于399的数量

timeout = N -- 超时请求的总数量

}

}

流量控制方法

wrk使用了多路复用的技术。多路复用使得用一个线程能够异步发起不少个请求,因此不太好用线程数来控制请求数。但一个http链接同时只能处理一个请求,因此能够按一次请求的latency估算出一个链接能够承载的qps数,调整链接数便可控制压测请求大小qps = 1000/latency * Connectnum。 这里须要注意的是单个线程只能占用一个cpu核心,当cpu到瓶颈时也可能压不上去,须要调整线程数。

另一个方法,把链接数设置的很是大,让链接数再也不是发压的瓶颈,而后调整脚本中的delayTime和线程数,能够精确控制qps。 qps = 1000/delayTime * threadnum

总结

在实际压测过程当中,我曾用一个线程压出过几十万qps,也好奇过为何一个线程能压出这么高的qps。咱们每次请求须要5ms,因此按道理一个线程只能压出200qps,那实际上几百倍的差别是如何来的?后来大体了解到wrk的做者使用了多路复用的技术(epoll,kqueue),每次请求后并非阻塞等在在那里,并且异步等待结果,同时也能够发起下一个请求,这和redis很像吧,其实wrk的做者代码都是抄的redis的,哈哈。

因此这里要注意-c和-t链接数和参数的设置,一个线程只能占用一个cpu核,若是还没到cpu的瓶颈,决定qps的是链接处和瓶颈响应时间,举个例子,若是只有一个线程,连接数10,平均响应时间10ms,那么一个连接一秒能过100个请求,因此总共能压出1000qps。当cpu到瓶颈后,无论怎么去调大链接数qps都不会上去,这个时候就须要考虑调大线程数了,利用多核心的资源提高qps。

最后附上咱们压测中实际使用的lua脚本,结构也比较简单,你们能够大体参考下。

local list = {}

local delaytime = 0 -- 默认delay是0ms

local filename = "reqdata.txt" -- 默认请求数据文件

setup = function(thread)

for k,v in pairs(wrk.addrs)

do

print(v)

end

end

init = function(args)

if (args[1] ~= nil) then

delaytime = args[1] -- 启动命令中能够指定延迟时间,如未指定,使用默认文件

end

if (args[2] ~= nil) then

filename = args[2] -- 启动命令中能够指定请求文件目录,如未指定,使用默认文件

end

math.randomseed(os.time())

local i = 0

for line in io.lines(filename) -- 把请求包体读入后写到list里,方便后续使用

do

list[i] = line

i = i+1

end

end

request = function()

wrk.body = list[math.random(0, #list)] -- 随机使用一个包体

wrk.method = "POST"

wrk.scheme = "http"

wrk.path = "/appstore/uploadLogSDK"

wrk.headers["Content-Type"]="application/x-www-form-urlencoded"

return wrk.format()

end

delay = function()

return delaytime

end

response = function(status, headers, body) --这里我没作特殊统计,只是在调试过程当中输出了一些内容

--print(status)

--print(body)

--print(wrk.format(wrk.method, wrk.path, wrk.headers, wrk.body))

--wrk.thread:stop()

end

done = function(summary, latency, requests)

print("99 latency:"..latency:percentile(99.0)) -- 这里我只是额外输出了99分位的延时,貌似数据不太对

end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值