|
|
楼主 |
发表于 2020-11-11 17:00:15
|
显示全部楼层
一、简介* u+ W8 ^# \8 q; M) m; w, ]
1 |: z% g2 p! z9 V* P( H3 h" J/ ]
wrk 是一款针对 Http 协议的基准测试工具,它能够在单机多核 CPU 的条件下,使用系统自带的高性能 I/O 机制,如 epoll,kqueue 等,通过多线程和事件模式,对目标机器产生大量的负载。$ @5 L, W8 E( H- Y! C1 r1 ?7 b
. W1 I2 e# }6 a0 k
wrk是开源的, 代码在 github 上:https://github.com/wg/wrk# H% v, L* }$ z
1 C5 D5 y! V4 J/ A& J3 v
安装:https://www.cnblogs.com/savorboard/p/wrk.html
d3 X2 K4 Z. f" h: v$ L
0 |# j$ F% A. a `5 k) W
$ e2 Z- Z* x" O
8 D+ g$ B$ C+ f4 U2 C8 ~优势:
9 Q, F C7 B% n2 f' B9 B: I; p9 h" U
轻量级性能测试工具
/ R! n2 q& i) l0 B安装简单
# \9 A1 `2 e; V4 x5 e2 \( `学习曲线基本为0,几分钟就学会使用了( C( M- P6 O4 [$ V$ z0 p
基于系统自带的高性能I/O机制,如epoll,kqueue,利用异步的事件驱动框架,通过很少的线程就可以压出很大的并发量,例如几万、几十万,这是很多性能测试工具无法做到的。
2 L1 z- _1 ?. B9 @5 O& f0 I劣势:
* L( @4 v6 a% K( W( O' m
, o4 E4 z8 m# T9 @* I+ ~wrk 目前仅支持单机压测,后续也不太可能支持多机器对目标机压测,因为它本身的定位,并不是用来取代 JMeter, LoadRunner 等专业的测试工具。
' h1 D( n3 S8 L+ w6 z2 i5 N
. N; b6 P8 h0 X. V; L5 Q) }5 `$ K5 B) U- k& R+ @; |
二、格式及用法
: O9 t3 N) `+ T& h* K7 m
8 C: {8 S& U, b1 V3 Z9 m# l复制代码
% A7 S' S( p. @8 @, F/ ^2 c% JUsage: wrk <options> <url> : e" f3 R; M$ w3 S
( N- b6 A! r8 X3 d% f% w* j2 `
Options:
7 a6 [6 o$ ?' a -c, --connections <N> Connections to keep open . k: K& y, R9 J2 l+ h6 v
-d, --duration <T> Duration of test
: a8 F0 X5 p4 T+ J0 F -t, --threads <N> Number of threads to use
5 b) b& F/ r5 j' h+ w 5 m$ \* C, p; `9 t; j* U% @
-s, --script <S> Load Lua script file ! |5 t/ s, A( ]. j9 I5 p- h6 _
-H, --header <H> Add header to request
9 p8 P6 R4 x4 I2 R6 m" B5 I --latency Print latency statistics * `1 M5 K2 B5 g
--timeout <T> Socket/request timeout
* [5 `1 P; t& y' o6 S$ C -v, --version Print version details - L8 t9 n- n' g) x6 S7 ]! K7 |
" u% a8 ] M5 X" ^& d4 X Numeric arguments may include a SI unit (1k, 1M, 1G)
6 s' k/ q; d5 N1 Q9 @6 X Time arguments may include a time unit (2s, 2m, 2h)) A4 B+ J, n& v5 j6 `! V$ B \
复制代码; \* ]4 d f& k2 \% }) K5 b
+ R- P$ Q3 a4 l e' T
9 o: R% x% R$ L I翻译成中文:
7 t% L) r6 V) [, g' E
! s' Q4 m# @4 U2 I' x6 w复制代码
% ^" g7 }' `' Z$ ]0 V使用方法: wrk <选项> <被测HTTP服务的URL> 5 e% J' S. W- W" ?) r
: b8 X. m$ a' s9 M Options: * @5 z# ~% [& V. i6 k9 w
-c, --connections <N> 跟服务器建立并保持的TCP连接数量
. O. B) u( d3 _8 J( x -d, --duration <T> 压测时间 6 o: l( S5 A( U1 z
-t, --threads <N> 使用多少个线程进行压测,压测时,是有一个主线程来控制我们设置的n个子线程间调度
3 `7 u: @0 L! [. i* I0 e C
6 n9 `6 [0 c P+ v2 Y) J- v8 X+ E6 M -s, --script <S> 指定Lua脚本路径 7 G! a! {# A) M+ h$ M
-H, --header <H> 为每一个HTTP请求添加HTTP头 / q* Z; ]4 t- U
--latency 在压测结束后,打印延迟统计信息 ! B$ X$ e c% s* v0 o4 F
--timeout <T> 超时时间
) e2 C0 M/ h3 q -v, --version 打印正在使用的wrk的详细版本信 6 o z, f: g: x& I& T
' H- [) r9 S. D, I0 X3 w0 o/ e) w <N>代表数字参数,支持国际单位 (1k, 1M, 1G)7 y, K$ J" w9 k% J
<T>代表时间参数,支持时间单位 (2s, 2m, 2h)1 a, }/ u% @2 V7 ~4 L* n; q- l
复制代码
5 O( P1 G7 m0 W* m$ j
S+ U8 w; |, R* d3 v% V- Z* r
, ]% J8 {) u, t) p
8 V0 D: {9 b( r( d) m6 D" _* _
4 H8 p8 E1 F& l4 ~% K三、简单压测及结果分析
; I; L+ y7 Q9 b- G( v+ ~5 M7 X. }& W' N' O, ~1 `( O
做一个简单的压测,分析下结果:0 l8 y9 o K8 Q" c& c- Q9 G
# {' O- o) F9 p; }8 Qwrk -t8 -c200 -d30s --latency http://www.bing.com/ K5 O) b6 g |. s/ S
- Y3 g! Y9 e; P1 q- F
( f8 a. U: n; n$ v, p7 ?. F输出:
; w( S3 s d; H! t) m% U5 p {& i- P
复制代码( ]4 }7 @ u0 ?1 Z4 [0 L
Running 30s test @ http://www.bing.com
: X4 w! H, [) R- X! @
: Y' X2 F1 D+ u& G+ b' Y, z8 J( o) ]# l 8 threads and 200 connections, U3 B3 J0 [3 W; \! a! C0 v
( c2 j$ M, z6 Y# x0 ^( _. v! a
Thread Stats Avg Stdev Max +/- Stdev* s: H9 }3 `6 @& X D" [( M
Latency 46.67ms 215.38ms 1.67s 95.59%& \% A4 B- w, b4 w
Req/Sec 7.91k 1.15k 10.26k 70.77%
( S+ F* B4 W0 @1 v" i- c9 a2 s
Latency Distribution
9 G D/ `1 _0 H 50% 2.93ms
# ]$ ]4 \5 `& z9 x0 \! ?, i 75% 3.78ms
0 y- h/ X/ L, }3 _; ^. y 90% 4.73ms
& Z r. H8 G2 _! }: M: }# V9 o 99% 1.35s9 m7 ^: G9 ~1 e1 R" |
1790465 requests in 30.01s, 684.08MB read4 A0 d; t2 t2 B6 Y
Requests/sec: 59658.29 J/ | r+ k* a2 \7 `, X
Transfer/sec: 22.79MB
2 ~/ U* N5 z' D/ t9 Q复制代码
- w" e! n% @; \. G0 s0 a, }. S* h以上是使用8个线程200个连接,对bing首页进行了30秒的压测,并要求在压测结果中输出响应延迟信息。
1 a6 u8 r2 h) N2 I$ E) ~ A4 A: U2 c3 R
以下是解释压测结果:0 I$ e" d- U8 ^- I
1 C8 W# `- @, U% G% e复制代码
2 v! k H# x$ ~6 @8 ^' p/ iRunning 30s test @ http://www.bing.com (压测时间30s)" s$ O: S1 H6 k% G
: d4 g# b; T# h 8 threads and 200 connections (共8个测试线程,200个连接)
% \: p( D% b8 N; h& Q" b8 ]1 e6 k q' G
Thread Stats Avg Stdev Max +/- Stdev) P5 G8 b9 S; t
(平均值) (标准差)(最大值)(正负一个标准差所占比例)
1 A8 `: `; |* { Latency 46.67ms 215.38ms 1.67s 95.59%
0 Q; @, O- P8 @2 w' n, Q (延迟)
& j& P, ~; R1 M7 p F( ^ Req/Sec 7.91k 1.15k 10.26k 70.77%& R5 u8 |! I" s. b
(处理中的请求数)1 P" K0 H8 k7 V4 f3 z& ?
2 O. X& _/ Y# ^, H Latency Distribution (延迟分布)
/ y2 ~* v9 f5 f; q+ N' q/ g 50% 2.93ms
4 c$ A6 |- c/ k+ M9 n 75% 3.78ms
Y* P0 C. z0 S/ z# ` 90% 4.73ms3 J# P" M; k9 G. j( H) D
99% 1.35s (99分位的延迟:%99的请求在1.35s以内) _8 l0 a @# R! c' l
1790465 requests in 30.01s, 684.08MB read (30.01秒内共处理完成了1790465个请求,读取了684.08MB数据)
* x9 W3 Y' p5 e2 O2 W* F, TRequests/sec: 59658.29 (平均每秒处理完成59658.29个请求)# o' ], c: y. x7 M$ E. ^& R
Transfer/sec: 22.79MB (平均每秒读取数据22.79MB)
" C% C! i* W7 @6 _% l/ }" G复制代码
0 x. V9 f! J0 g, a - S% ?7 ?. y5 L( `9 O
: E( y" E; {: S4 N - f2 m4 w. t8 ~- j. Y1 f
# Q8 |" {' s2 X8 N, k& L2 K四、使用lua脚本进行压测
2 z( c: f8 A7 i: H- G+ `7 j, l, ^& u' n
lua脚本是一种轻量小巧的脚本语言,用标准c语言编写,并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为程序提供灵活的扩展和定制功能。wrk工具嵌入了lua脚本语言,因此,在自定义压测场景时,可在wrk目录下使用lua定制压测场景。
* y* t' M' N8 B5 n7 v4 b2 [. E8 O
' ^7 A+ _: t7 ]8 Z% X 1、lua声明周期# S; F. a& E1 M6 w3 t: p7 f( g
4 h& n$ w0 O6 @' W- n3 N 共有三个阶段,启动阶段,运行阶段,结束阶段。wrk支持在这三个阶段对压测进行个性化。 8 D# i- w7 B, f% d2 i. [4 Y W
0 D2 y g) H$ e6 r/ @启动阶段" K' ?$ n1 Q: `7 e U
function setup(thread)
( R0 s) W0 ^) u l- v* r( E
' X% k* d# |* W- T/ u# l
$ f: o6 b) E t" w9 N在脚本文件中实现setup方法,wrk就会在测试线程已经初始化但还没有启动的时候调用该方法。wrk会为每一个测试线程调用一次setup方法,并传入代表测试线程的对象thread作为参数。setup方法中可操作该thread对象,获取信息、存储信息、甚至关闭该线程。
% W( ~* V5 U: P% n; Z6 m1 r% d/ z6 R# p7 G1 T3 Z
thread.addr - get or set the thread's server address& p0 i `2 {4 i0 {4 @1 P
thread:get(name) - get the value of a global in the thread's env
% t1 ]2 d4 {) C N% B1 Ethread:set(name, value) - set the value of a global in the thread's env
+ J. w( T1 y) H; P' Y% A) nthread:stop() - stop the thread& A( D7 S9 ?* l: Q) u
* |0 N" p/ |2 M# G
0 m6 }1 Q$ q7 q' l
运行阶段
2 m/ ?1 t% _7 ffunction init(args) --由测试线程调用,只会在进入运行阶段时,调用一次。支持从启动wrk的命令中,获取命令行参数;
; d/ b& U* ]6 l6 t+ |function delay() --在每次发送request之前调用,如果需要delay,那么delay相应时间;
) K/ M2 Y+ m1 i6 n% Efunction request() --用来生成请求;每一次请求都会调用该方法,所以注意不要在该方法中做耗时的操作;" \& {# ?: d& | P% c
function response(status, headers, body) --在每次收到一个响应时调用;为提升性能,如果没有定义该方法,那么wrk不会解析headers和body;# Z2 {0 S. G! Q; [
+ h' N# Q) c' D* W* F3 B/ m8 R' p3 O( q5 _0 q
结束阶段6 N2 v3 g( y8 K" z3 D' u1 r, {
function done(summary, latency, requests) --在整个测试过程中只会调用一次,可从参数给定的对象中,获取压测结果,生成定制化的测试报告。
/ L6 e" q/ D2 k
4 @3 L+ f1 w2 y$ `' x
. J* {5 |& e/ d' g+ s 2、自定义脚本中可访问的变量和方法:9 w' y0 Z% I1 T" s+ L( L
3 c2 V2 b) _( `! s h B0 p
变量:wrk
7 v: g) z1 x# ^. j, z' V! D3 y, R$ U+ h8 u: V
复制代码
, c% _- z1 |1 }% Bwrk = {# w1 j) k1 O$ M+ }' g O5 C4 C
scheme = "http",
) Y' z( r _' F7 }1 t4 v9 R- \ host = "localhost",
5 w2 Z" M3 T' {2 Y0 u0 v port = nil,; B* {' C+ m9 r
method = "GET",! \1 _' Z( ~1 |/ u
path = "/",
, \4 w) ~: D9 y; c4 d headers = {},
3 l. s% f+ `! Z. u body = nil,
4 Z2 k& Y- Q9 Y$ B" |6 Q thread = <userdata>,
% [: K. |3 W! J" {- q- v }7 P0 L% M4 J9 A- C1 R' f. W0 g' k6 G
复制代码( `1 [5 g, n' [0 y* e8 @4 J
( z& w, V1 G7 o" m
9 V! L U5 q) g2 Z* h6 E. s 方法:wrk.fomat wrk.lookup wrk.connect / [/ s* F1 c" Q& ?" A
" n! K" P8 p6 f8 Hfunction wrk.format(method, path, headers, body) --根据参数和全局变量wrk,生成一个HTTP rquest string。
2 b6 ^9 q2 o, U. N- H3 j8 y, h |function wrk.lookup(host, service) --给定host和service(port/well known service name),返回所有可用的服务器地址信息。+ v5 o: {3 y6 K5 P3 z' P( S
function wrk.connect(addr) --测试与给定的服务器地址信息是否可以成功创建连接
. C% @; q4 L( U i' Q8 P; A [
4 j0 V; `3 b5 h: v, {) N/ ^5 |( T% K1 h7 P' o) U, }
3、lua脚本压测实例
# X: \- B0 R! R% ~! r
, M+ s7 n% P- ~$ t" I 压测命令:wrk -t8 -c200 -d30s --latency -s test.lua http://www.bing.com
; [+ c1 M) y3 o3 k7 X: e$ u" N
1 J/ f& B3 m9 } test.lua是用lua写的压测脚本,如下是压测脚本的实例:
d4 t% l8 g5 D1 ` L. x. F# g0 l6 t& b7 Q% U
使用post方法压测
) t: \" \1 t8 |! G# H+ ~. N8 [( n* u% E8 a8 o' f- b _
复制代码
E! l, F! R8 k; ^wrk.method = "POST"* u4 A9 D1 g1 v3 T* D' e7 e; U
wrk.headers["S-COOKIE2"]="a=2&b=Input&c=10.0&d=20191114***"
+ e$ P( _( E2 U, O+ Nwrk.body = "recent_seven=20191127_32;20191128_111"
) J2 G3 Q7 p& R Z: V( Bwrk.headers["Host"]="api.shouji.**.com". x. R$ o" c/ t) [, Y
" S- U# ^) t9 Y
function response(status,headers,body)
. f( T+ j% o. v. ] if status ~= 200 then --将服务器返回状态码不是200的请求结果打印出来
) Y+ @$ T% ^* x' C$ M5 n print(body)
8 z- r7 `0 b$ }% @ -- wrk.thread:stop()3 G% o+ f _/ ]3 W
end' h, u7 A9 b5 J- d0 y- {2 R5 A4 t
end
' _. N, @4 l0 {! G$ Y, z% n3 j/ Q复制代码6 A5 O# \8 Q* e; N
' J: k4 |. ?1 Q+ z+ E# l
/ W) ]$ G }8 h: J2 `- c 发送json6 a6 l3 b1 u) z1 J1 q( D4 S
7 E: g8 d w% s* H5 j复制代码2 M" P; Z o# y8 C; i# E, p
request = function()! v' v% D3 O( z
local headers = { }
" |7 ~: i' Z; i2 Y1 L. @7 L- A headers['Content-Type'] = "application/json"
, x8 q- B# [6 d( Q% V body = {* U1 U) n6 G+ J# h. y" y r2 \
mobile={"1533899828"},
! |4 ^# N& L5 U! o params={code=math.random(1000,9999)}" {3 U% G9 D& n* N3 W: x/ ^ h
}2 }. z) p; E5 o; C/ O+ z: S
local cjson = require("cjson"), j$ D* A/ o9 A e( h/ D
body_str = cjson.encode(body)$ D4 E! T) f( o, B% c
return wrk.format('POST', nil, headers, body_str)
~# i5 Z+ w: N6 N& m6 n- s9 |end
3 h0 a6 O4 m5 b+ Z& S: R复制代码1 P) C( p4 r- z1 q. {. ]
1 Z1 c' e/ [4 z( C7 A9 n/ ~" |9 |+ R2 ^% u
wrk读取文件,实现随机header-cookie
" N; k$ k v: }4 Q* A1 F# x8 a1 m( s4 b8 x; B H" x, H: g
复制代码
4 R: V+ e9 |9 e4 pidArr = {}
% n5 v1 d+ j1 k8 b" \* A" T8 i( r! Yfalg = 0
p4 \, H! a$ xwrk.method = "POST"
" q# R) _$ x E8 D" Z: _wrk.body = "a=1"
7 s) k' @/ w0 Bfunction init(args)
1 a Q# J& X+ ?! b R for line in io.lines("integral/cookies.txt") do, ^/ A. g4 v4 A5 M* V0 v/ N
print(line)
# \$ h' ]# e' M* I$ V4 X f& C idArr[falg] = line1 L! N+ z1 [2 e: F, D7 }
falg = falg+1: o: w6 T0 w3 }, `
end7 {" F _; y0 M/ m8 |; m
falg = 09 R* x- Q/ L% i# _/ v t$ R
end+ ~2 C7 n4 \% z3 _0 f, ?% {: [
' Z& B" x, e' j--wrk.method = "POST"6 O9 _9 y- Z! Q1 E( ]" I
--wrk.body = "a=1"- n5 P, I8 N2 [" o6 p% {4 \. p# k
--wrk.path = "/v1/points/reading"
# {8 w$ f- b; `7 f
; C9 ^4 o$ t' K( _: ?request = function()
$ \) B @1 B0 u* o& \3 s parms = idArr[math.random(0,4)] --随机传递文件中的参数
8 g' B B# S6 `' n( c' l; J --parms = idArr[falg%(table.getn(idArr)+1)] 循环传递文件中的参数
$ U8 u: o7 G( K wrk.headers["S-COOKIE2"] = parms: C- w$ H" ^- I7 O* @
falg = falg+15 ~. M1 L2 F( {
return wrk.format()/ V$ M0 d5 G# T
end9 N' {- o6 ~7 f
复制代码
# N0 v4 u/ g2 D$ a: J( r1 F
4 c4 U; Y) ]2 u) w( H9 J k2 z! x" O3 z0 n3 t
wrk创建数组并初始化,拼接随机参数$ ?# Y: f- P9 M) h( n
' o: X9 ^1 e) ~, {, s$ W复制代码
* E2 T" K1 ^* e6 U+ M( X( MidArr = {};
4 {! y0 Z8 J9 o3 ~5 h3 t% Nfunction init(args)
2 y, U7 a* F* x' g idArr[1] = "1";& @6 h9 h6 h" z0 s( @
idArr[2] = "2";
s) f' L& h& Y! R6 {3 A4 @ idArr[3] = "3";
1 @3 T3 _8 d+ x) I) v idArr[4] = "4";' i+ C; l; S2 t- b$ Z5 [( m' a3 ^
end
4 O& r0 h+ @8 @/ b v V# o
0 }. _9 P2 O3 Z3 h/ |request = function()
|" J; N7 w4 N; ]1 J parms = idArr[math.random(1,4)]) N1 O3 k+ K7 B
path = "/v1/points/reading?id="..parms
2 @9 S' L. H5 c' R7 z" ` return wrk.format("GET",path)
/ d) H8 S$ q; |( v9 `9 b' E, b: X# Yend( n! o; N. k) M9 {8 V4 h
复制代码 |
|