一、wrk
wrk 只能运行在 Unix 类的系统上. 比如 linux, mac, solaris 等. 也只能在这些系统上编译. wrk 的一个很好的特性就是能用很少的线程压出很大的并发量. 原因是它使用了一些操作系统特定的高性能 io 机制, 比如 select, epoll, kqueue 等. 其实它是复用了 redis 的 ae 异步事件驱动框架. 确切的说 ae 事件驱动框架并不是 redis 发明的, 它来至于 Tcl的解释器 jim, 这个小巧高效的框架, 因为被 redis 采用而更多的被大家所熟知.
CentOS环境下安装wrk
sudo yum groupinstall 'Development Tools'
sudo yum install openssl-devel
sudo yum install git
git clone https://github.com/wg/wrk.git wrk
cd wrk
make
# 把生成的wrk移到一个PATH目录下面, 比如
sudo cp wrk /usr/local/bin
基本压测
wrk -t12 -c400 -d30s http://127.0.0.1:8080/index.html
使用12个线程运行30秒, 400个http并发
命令行选项
-c, --connections: 总的http并发数
-d, --duration: 持续压测时间, 比如: 2s, 2m, 2h
-t, --threads: 总线程数(一般线程数不宜过多,核数的2到4倍足够,wrk不是使用每个连接一个线程的模型,而是通过异步网络io提升并发量,所以网络通信不会阻塞线程执行)
-T, --timeout: 设置http超时时间(默认是1S)
-s, --script: lua脚本,使用方法往下看
-H, --header: 添加http header, 比如. "User-Agent: wrk"
--latency: 在控制台打印出延迟统计情况
--timeout: http超时时间
返回结果
Latency:响应时间
Req/Sec:每个线程每秒钟的执行的连接数
Avg:平均
Max:最大
Stdev:标准差
+/- Stdev: 正负一个标准差占比
Requests/sec:每秒请求数(也就是QPS),这是一项压力测试的性能指标,通过这个参数可以看出吞吐量
Latency Distribution,如果命名中添加了—latency就会出现相关信息
lua脚本压测
在基本压测中, 每次发送的请求都是一样的,很多时候我们压测的请求体是每个请求都不一样, 这时候就要写lua基本来压测
压测JSON格式的POST接口示例
updateUser.lua(wrk.body后的数据,最好放在同一行内)
-- example HTTP POST script which demonstrates setting the
-- HTTP method, body, and adding a header
wrk.method = "POST"
wrk.body = "{\"header\": {\"requestTime\":\"2018120910320000\", \"requestSeq\": \"e379853d-d93e-4249-b9be-8a272b\"}, \"content\": {\"id\": 1 }}"
wrk.headers["Content-Type"] = "application/json"
命令行执行
wrk -t12 -c100 -d30s -T2s -s updateUser.lua --latency http://127.0.0.1:9100/updateUser
30秒后,看到压测的结果
Running 30s test @ http://127.0.0.1:9100/updateUser
12 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 219.49ms 145.69ms 1.00s 59.02%
Req/Sec 38.30 18.40 110.00 66.07%
Latency Distribution
50% 211.10ms
75% 281.05ms
90% 432.63ms
99% 620.00ms
13629 requests in 30.10s, 3.63MB read
Requests/sec: 452.78
Transfer/sec: 123.42KB
二、jmeter
updateUser.jmx
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="4.0" jmeter="4.0 r1823414">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="测试计划" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="线程组" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="循环控制器" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">1</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">10</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="updateUser" enabled="true">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{
"header": {
"requestTime": "2018100910320000",
"requestSeq": "e379853d-d93e-4249-b9be-8a272b"
},
"content": {
"id": 1
}
}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">192.168.124.129</stringProp>
<stringProp name="HTTPSampler.port">9001</stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/updateUser</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="响应断言" enabled="true">
<collectionProp name="Asserion.test_strings">
<stringProp name="527098352">"respCode":"1",</stringProp>
</collectionProp>
<stringProp name="Assertion.custom_message"></stringProp>
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
<boolProp name="Assertion.assume_success">false</boolProp>
<intProp name="Assertion.test_type">2</intProp>
</ResponseAssertion>
<hashTree/>
</hashTree>
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="察看结果树" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP信息头管理器" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="" elementType="Header">
<stringProp name="Header.name">Content-Type</stringProp>
<stringProp name="Header.value">application/json;charset=UTF-8</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
新建一个run.sh脚本,方便我们执行目标jmx压测脚本
#! /bin/bash
rm -rf ./html/* ./out/*
cd bin
sh ./jmeter.sh -n -t /home/jmeter/apache-jmeter-4.0/scripts/$1.jmx -l /home/jmeter/apache-jmeter-4.0/out/result.jtl -e -o /home/jmeter/apache-jmeter-4.0/html
准备完毕,我们执行
sh run.sh updateUser
可以看到result文件夹内,出现了测试结果。
三、ab
安装
yum -y install httpd-tools
验证安装是否成功
ab -V
简单测试命令示例
a. 同时10个请求,连续点击1000次(每次Request完毕自动销毁,重新请求)
ab -n 1000 -c 10 http://www.test.com/index.html
b. 同时10个请求,连续点击1000次,并保持Keep-Alive方式(保持长连接的方式)
ab -n 1000 -c 10 -k http://www.test.com/index.html
c. 同时10个请求,请求测试时间为20s
ab -c 10 -t 20 http://www.test.com/index.html
d. 将请求性能详情输出到CSV档文件
ab -e output.csv -n 1000 -c 10 http://www.test.com/index.html
out.csv: {请求序号 - 请求耗时ms}
e. 包含Post的数据的文件,例如POST发送JSON格式数据
ab -p p.json -T application/json -n 1000 -c 10 http://www.test.com/index.html
测试用的Json报文updateUser.json
{
"header": {
"requestTime": "2018100910320000",
"requestSeq": "e379853d-d93e-4249-b9be-8a272b"
},
"content": {
"id": 1
}
}
执行
ab -p updateUser.json -T application/json -c 10 -t 10 http://192.168.124.129:9001/updateUser
-n在测试会话中所执行的请求个数。默认时,仅执行一个请求。请求的总数量
-c一次产生的请求个数。默认是一次一个。请求的用户量
-t测试所进行的最大秒数。其内部隐含值是-n 50000,它可以使对服务器的测试限制在一个固定的总时间以内。默认时,没有时间限制。
控制台出现测试结果,压测结果参数说明
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking www.myvick.cn (be patient).....done
Server Software: nginx/1.13.6 #测试服务器的名字
Server Hostname: www.myvick.cn #请求的URL主机名
Server Port: 80 #web服务器监听的端口
Document Path: /index.php #请求的URL中的根绝对路径,通过该文件的后缀名,我们一般可以了解该请求的类型
Document Length: 799 bytes #HTTP响应数据的正文长度
Concurrency Level: 10 # 并发用户数,这是我们设置的参数之一
Time taken for tests: 0.668 seconds #所有这些请求被处理完成所花费的总时间 单位秒
Complete requests: 100 # 总请求数量,这是我们设置的参数之一
Failed requests: 0 # 表示失败的请求数量,这里的失败是指请求在连接服务器、发送数据等环节发生异常,以及无响应后超时的情况
Write errors: 0
Total transferred: 96200 bytes #所有请求的响应数据长度总和。包括每个HTTP响应数据的头信息和正文数据的长度
HTML transferred: 79900 bytes # 所有请求的响应数据中正文数据的总和,也就是减去了Total transferred中HTTP响应数据中的头信息的长度
Requests per second: 149.71 [#/sec] (mean) #吞吐率,计算公式:Complete requests/Time taken for tests 总请求数/处理完成这些请求数所花费的时间
Time per request: 66.797 [ms] (mean) # 用户平均请求等待时间,计算公式:Time token for tests/(Complete requests/Concurrency Level)。处理完成所有请求数所花费的时间/(总请求数/并发用户数)
Time per request: 6.680 [ms] (mean, across all concurrent requests) #服务器平均请求等待时间,计算公式:Time taken for tests/Complete requests,正好是吞吐率的倒数。也可以这么统计:Time per request/Concurrency Level
Transfer rate: 140.64 [Kbytes/sec] received #表示这些请求在单位时间内从服务器获取的数据长度,计算公式:Total trnasferred/ Time taken for tests,这个统计很好的说明服务器的处理能力达到极限时,其出口宽带的需求量。
Connection Times (ms)
min mean[+/-sd] median max
Connect: 1 2 0.7 2 5
Processing: 2 26 81.3 3 615
Waiting: 1 26 81.3 3 615
Total: 3 28 81.3 6 618
Percentage of the requests served within a certain time (ms)
50% 6
66% 6
75% 7
80% 7
90% 10
95% 209
98% 209
99% 618
100% 618 (longest request)
#Percentage of requests served within a certain time(ms)这部分数据用于描述每个请求处理时间的分布情况,比如以上测试,80%的请求处理时间都不超过7ms,这个处理时间是指前面的Time per request,即对于单个用户而言,平均每个请求的处理时间