JMeter的安装及性能测试方案
1. JMeter的安装
安装JDK环境:
rpm -ivh jdk-8u171-linux-x64.rpm
java -version
java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)
安装 jmeter:官方下载地址
tar xvf jmeter.tar.gz
mv apache-jmeter-5.4.1/ jmeter
vim /etc/profile
export JMETER_HOME=/root/jmeter
export CLASSPATH=$JMETER_HOME/lib/ext/ApacheJMeter_core.jar:$JMETER_HOME/lib/jorphan.jar:$CLASSPATH
export PATH=$JMETER_HOME/bin:$PATH:$HOME/bin
source /etc/profile
jmeter -v
Jan 30, 2023 11:47:41 AM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
_ ____ _ ____ _ _ _____ _ __ __ _____ _____ _____ ____
/ \ | _ \ / \ / ___| | | | ____| | | \/ | ____|_ _| ____| _ \
/ _ \ | |_) / _ \| | | |_| | _| _ | | |\/| | _| | | | _| | |_) |
/ ___ \| __/ ___ \ |___| _ | |___ | |_| | | | | |___ | | | |___| _ <
/_/ \_\_| /_/ \_\____|_| |_|_____| \___/|_| |_|_____| |_| |_____|_| \_\ 5.4.1
Copyright (c) 1999-2021 The Apache Software Foundation
2. JMeter测试方案
测试模板(search.jmx)请在本机Window下运行jmeter GUI来生成,保存后再上传到测试服务器上。
下面是根据模板自己修改后的一个测试模板文件:
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.4.1">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="测试" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">true</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="服务器变量" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="threadNum" elementType="Argument">
<stringProp name="Argument.name">threadNum</stringProp>
<stringProp name="Argument.value">${__P(threadnum,)}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="time" elementType="Argument">
<stringProp name="Argument.name">time</stringProp>
<stringProp name="Argument.value">${__P(time,)}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="links" elementType="Argument">
<stringProp name="Argument.name">links</stringProp>
<stringProp name="Argument.value">172.17.15.31</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="port" elementType="Argument">
<stringProp name="Argument.name">port</stringProp>
<stringProp name="Argument.value">8888</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="http" elementType="Argument">
<stringProp name="Argument.name">http</stringProp>
<stringProp name="Argument.value">http</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</Arguments>
<hashTree/>
<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="调试变量" enabled="false">
<collectionProp name="Arguments.arguments">
<elementProp name="threadNum" elementType="Argument">
<stringProp name="Argument.name">threadNum</stringProp>
<stringProp name="Argument.value">5000</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="time" elementType="Argument">
<stringProp name="Argument.name">time</stringProp>
<stringProp name="Argument.value">300</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="links" elementType="Argument">
<stringProp name="Argument.name">links</stringProp>
<stringProp name="Argument.value">172.17.15.31</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="port" elementType="Argument">
<stringProp name="Argument.name">port</stringProp>
<stringProp name="Argument.value">8888</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="http" elementType="Argument">
<stringProp name="Argument.name">http</stringProp>
<stringProp name="Argument.value">http</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</Arguments>
<hashTree/>
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="察看结果树" enabled="false">
<boolProp name="ResultCollector.error_logging">true</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>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<ResultCollector guiclass="StatVisualizer" 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>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<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>
<intProp name="LoopController.loops">-1</intProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">${threadNum}</stringProp>
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
<boolProp name="ThreadGroup.scheduler">true</boolProp>
<stringProp name="ThreadGroup.duration">${time}</stringProp>
<stringProp name="ThreadGroup.delay">1</stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">false</boolProp>
</ThreadGroup>
<hashTree>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP信息头管理器" enabled="false">
<collectionProp name="HeaderManager.headers">
<elementProp name="" elementType="Header">
<stringProp name="Header.name">Content-Type</stringProp>
<stringProp name="Header.value">application/json</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="search" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">${links}</stringProp>
<stringProp name="HTTPSampler.port">${port}</stringProp>
<stringProp name="HTTPSampler.protocol">${http}</stringProp>
<stringProp name="HTTPSampler.contentEncoding">utf-8</stringProp>
<stringProp name="HTTPSampler.path">/c/suggestion?appKey=dcf09be0f8993e896ba4de940f1692c3&channelCode=50000138&keyword=${__RandomString(5,abcdefghijklmnopqrstuvwxyz,)}</stringProp>
<stringProp name="HTTPSampler.method">GET</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.implementation">Java</stringProp>
<stringProp name="HTTPSampler.connect_timeout">30000</stringProp>
<stringProp name="HTTPSampler.response_timeout">30000</stringProp>
<stringProp name="TestPlan.comments">http://172.xxx.xxx.31:8888/c/suggestion?appKey=dcf09be0f8993e8960f1692c3&channelCode=5012345&keyword=ab</stringProp>
</HTTPSamplerProxy>
<hashTree>
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="响应断言" enabled="false">
<collectionProp name="Asserion.test_strings"/>
<stringProp name="Assertion.custom_message"></stringProp>
<stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
<boolProp name="Assertion.assume_success">false</boolProp>
<intProp name="Assertion.test_type">2</intProp>
</ResponseAssertion>
<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>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
mkdir /root/jmeter/log
mkdir /root/jmeter/script
ls /root/jmeter/script/
search.jmx
cd /root/jmeter
vim start.sh
jmeter -J threadnum=$1 -J time=$2 -n -t ./script/search.jmx -l ./log/log$1-$2-$3.txt -e -o ./log/report$1-$2-$3
chmod +x start.sh
# -n:以非GUI形式运行Jmeter
# -t:运行JMX测试计划脚本文件的路径,./script/search.jmx
# -l:运行结果保存路径(.jtl),此文件必须不存在
# -j: jmeter运行日志
# -r: 运行分布式压测服务器,指明用jmeter属性"remote_hosts"
# -R:运行分布式服务器,其后跟着服务器列表
# -e:在脚本运行结束后生成html报告,此参数要与-l一起使用。
# -o:用于存放html报告的目录,此目录必须为空。此参数与-e一起使用。
./start.sh 5000 300 1
3. JMeter分布式集群配置
由于单台压测服务器并不足以进行创建过多线程时,则可以使用分布式测试。将所有测试线程都由各台压测服务器同时产生,并将测试结果返回到控制服务器。控制服务器与压测服务器可以是同一台服器。
要求:
- 压测服务器、控制服务器、被测试服务器要在同一内网;
- 压测服务器、控制服务器安装的java版本一致;
- 压测服务器、控制服务器运行的jmeter版本一致;
压测服务器、控制服务器的jmeter的SSL设置:
所有压测服务器与控制服务器的通讯,默认以ssl证书通讯。可以在控制服务器中使用./create-rmi-keystore.sh 命令(jmeter软件bin目录下中自带)产生.jks后缀名的证书,将此证书存放到各压测服务器的jmeter软件的bin目录下。
个人不太建议使用SSL证书通讯方式,本人实际测试时,在1099端口开放且并未占用的正常情况下,压测服务器并未收到控制服务器的测试指令的问题,用telnet测试端口可以正常远程连接。所以开启SSL不靠谱。
所以建议在jmeter.properties配置文件中,关闭SSL选项:
server.rmi.ssl.disable=true
注意每台压测服务器和控制服务器都要修改此选项,这样就不需要SSL证书了。
在每台压测服务器上运行压测服务:
./jmeter-server -Djava.rmi.server.hostname=内网IP(被测服务器IP)
在控制服务器上,设置压测服务器IP,打开jmeter.properties文件,修改remote_hosts配置值:
vim ./bin/jmeter.properties
# remote_hosts=127.0.0.1 注销此条
remote_hosts=192.168.24.11:1099,192.168.24.12:1099
多台压测服务器之间用逗号相隔。1099是默认通讯端口,可省略写入。
如果压测服务器的侦测端口1099被修改了,那么的remote_hosts的IP地址后面必须写上端口
#server_port=1099
测试端口是否被占用:
ss -tnlp | grep 1099
一切准备就绪。在控制器启动测试:
./jmeter -n -t ./Test_Plan.jmx -r -l ./result.jtl -e -o ./html
Test_Plan.jmx是测试计划文件, result.jtl是测试结果文件。
4. JMeter参数配置说明
线程数:指测试时要发起压测的线程数量,相当于模拟访问用户数。如果是分布式压力测试,则每台压测服务器都会按这个数量产生线程,而不是整个测试的总线程数。
Ramp-Up时间:所有线程创建完成所需的时间,以上方30秒设置为例,则30秒时间产生10000个线程,每秒则产生10000/30 = 约 999 条线程;
循环次数:每个线程循环执行的次数,勾选“永远”则表示不限次数地循环执行,一直到下方的“持续时间”结束为止,如果所有线程循环完预定的次数,则当前测试就会停止了,就算下方的“持续时间”到了实际测试也已经是完成了。
持续时间(秒):当勾选“调度器”时,表示整个测试要持续执行的时间。实际测试时并非完全遵循此时间结束,如果上方的“循环次数”设置一个数值时,就算已经在测试时已经过了设定的持续时间,如果线程未循环完预定的循环次数,仍然会循环执行完为止。
启动延迟(秒):测试开始时需延迟多少秒才开始。
“same user on each iteration":表示每次循环是否使用同一用户(即同一线程)执行,这对于web测试是否需要保持同一会话时有用;
”延迟创建线程直到需要“:如选项如果勾选,则在Ramp-Up时间结束后等所有线程都启动完毕才开始执行测试,如果Ramp-Up时间未结束时,已创建的线程则在等待状态,并未开始执行测试。实际测试中,此选项几乎不会影响测试结果 。
5. FAQ
- 如果遇到 open too many files,说明遇到打开文件最大值问题。可进行如下设置:
echo "ulimit -HSn 65536" >> /etc/rc.local
echo "ulimit -HSn 65536" >> /root/.bash_profile
ulimit -HSn 65536
- 如果遇到out of memory异常,则增大JVM内存池。编辑jmeter文件,找到:
vim bin/jmeter
"${HEAP:="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"}"
"${HEAP:="-Xms4096m -Xmx4096m -XX:MaxMetaspaceSize=256m"}"
- 检查TCP/IP连接状态和对应个数:
netstat -an | awk '/^tcp/ {++s[$NF]} END {for(a in s) print a, s[a]}'