Jmeter基本使用与常见性能瓶颈
一、什么是Jmeter
Apache JMeter 是 Apache 组织基于 Java 开发的压力测试工具,用于对软件做压力测试。
JMeter 最初被设计用于 Web 应用测试,但后来扩展到了其他测试领域,可用于测试静态和动态资源,如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库和 FTP 服务器等等。JMeter 可对服务器、网络或对象模拟巨大的负载,
在不同压力类别下测试它们的强度和分析整体性能。另外,JMeter 能够对应用程序做功能/回归测试,通过创建带有断言的脚本来验证程序是否返回了期望结果。为了最大限度的灵活性,JMeter 允许使用正则表达式创建断言。
二、Jmeter中的相关概念
- 线程:一个线程可以理解为一个虚拟用户。比如10个线程表示有10个用户不间断发起请求,也可以理解为并发数
- 线程组:测试的起点。一个测试计划必须包含至少一个线程组,其余所有组件都必须在线程组下
- 监听器:用来查看测试过程中的各项指标
- 取样器(Sample)是性能测试中向服务器发送请求,记录响应信息,记录响应时间的最小单元。JMeter 原生支持多种不同的sampler ,如 HTTP Request Sampler 、 FTP Request Sample 、TCP Request Sample 、JDBC Request Sampler 等,每一种不同类型的 sampler 可以根据设置的参数向服务器发出不同类型的请求。(实际上就是配置请求地址和请求参数的地方)
- 定时器(Timer)用于操作之间设置等待时间,等待时间是性能测试中常用的控制客户端QPS的手端。类似于LoadRunner里面的“思考时间”。JMeter 定义了Bean Shell Timer、Constant Throughput Timer、固定定时器等不同类型的Timer。
三、压力测试过程
本小节以单一请求为例,描述压力测试设置请求参数、查看测试结果的过程
- 从官网下载jmeter,解压缩
- Windows下打开bin/jmeter.cmd
- 新建测试计划与线程组。打开后默认已建立一个测试计划,右击测试计划根据需要新建线程组,一般选择“setUp Thread Group(常规线程组)”或“Stepping Thread Group(阶梯测试线程组)”
- 右击线程组根据需要新建取样器(设置请求参数的地方),一般我们是新建HTTP Request
- 右击线程组,根据需要查看的指标新建监听器,一般结果树(view Result Tree)和聚合报告(Summary Report)是必需的,前者可以看到jmeter发出的每个请求的响应情况,后者可以在生成实时汇总报告
#线程组参数说明
- 线程数: 同时发起的请求数。如线程数为10表示用10个线程模拟10个用户发起请求
- Ramp-Up时间: 多长时间内启动完全部线程
- 循环次数:每个线程循环请求的次数,设置为永远则所有线程持续不间断请求
- 持续时间: 测试请求的时间
- 启动延迟: 单一脚本时用不上。此参数表示当有多个jmeter脚本时,间隔多长时间启动下一脚本
-
因为jmeter图形化界面本身会占用一定资源,为了最大化资源利用效率,jmeter官方也推荐在命令行下进行压力测试,而仅在图形界面设置参数与查看最终结果,因此以上设置完成之后需要保存为jmx文件
-
将jmx文件与jmeter压缩包一起上传到linux服务器或其他有命令行的地方,执行以下命令开始压测
jmeter -n -t ./xxx.jmx -l ../xxx.jtl -e -o ../xxx #参数说明: - -n 表示以命令行执行脚本 - -t 指明测试脚本文件的位置(测试脚本文件为jmx文件) - -l 指明测试结果保存的位置(测试结果文件为jtl文件) - -e -o 表示同时生成网页报告以及网页文件存放的目录,该目录应为空或不存在
-
将测试结果文件(jtl文件)拿出来,导入到jmeter图形界面中,查看测试结果;或将网页文件拿出来在网页上查看测试结果
四、测试结果查看
- 测试过程中的实时查看:
在命令行执行脚本测试过程中,会看到以上页面。
- “summary +”表示测试过程中的变化量,“summary =”所在的行才是我们需要关注的测试结果,其后跟的数字代表发送请求的数量
- in 后面跟的是时间
- = 后面跟的是吞吐量,可以近似看作QPS、TPS
- Avg、Min、Max 表示请求响应的平均时间、最小时间、最大时间
- Err 后面跟的是错误请求的数量与百分比
- Active、Started、Finished表示线程数
如前三行可以解读为:
- 在前23秒的时候发起了767次请求,TPS为33.3/s,平均响应时间为5724ms,最小响应时间为142ms,最大响应时间为22562ms,此时有0个错误请求,错误请求所占比例为0%,此时启动的线程数是500个,活跃的线程数是500个,已经结束的线程数为0个
- 在其后的30s內,共发起1212次请求,这30s內TPS为40.4/s,平均响应时间为12531ms,最小响应时间为394ms,最大响应时间为49573ms,此过程中共有0个错误请求,错误请求所占比例为0%,此时启动的线程数是500个,活跃的线程数是500个,已经结束的线程数为0个
- 总结以上,在前53s时间内,共发起1979次请求,TPS为37.3,平均响应时间为9893ms,最小响应时间为142ms,最大响应时间为49573ms,此时有0个错误请求,错误请求所占比例为0%,此时启动的线程数是500个,活跃的线程数是500个,已经结束的线程数为0个
-
测试结束后将结果文件(jtl文件)导入到jmeter图形界面:
结果树:
结果树可以看到每个请求的响应情况
聚合报告:
聚合报告可以看到发起的总请求数、平均响应时间、响应时间的中位数、90%的请求在多长时间内完成响应、95%的请求在多长时间内完成响应、99%的请求在多长时间内完成响应、响应时间的最大值、响应时间的最小值、异常请求所占的比例、吞吐量(TPS)以及发送、接受数据的速度
Tips
-
如果调整参数每次都在图形界面进行,对于我们在命令行进行测试十分不方便,我们可以直接修改jmx文件实现参数调整,如果线程组使用的是“setUp Thread Group”,那么可以在jmx文件中全局搜索“setUp”关键字,找到以下内容修改:
<SetupThreadGroup guiclass="SetupThreadGroupGui" testclass="SetupThreadGroup" testname="setUp Thread Group" enabled="true"> <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> <boolProp name="LoopController.continue_forever">false</boolProp> <intProp name="LoopController.loops">-1</intProp> </elementProp> <stringProp name="ThreadGroup.num_threads">200</stringProp> <!--线程数--> <stringProp name="ThreadGroup.ramp_time">1</stringProp> <!--多长时间内启动所有线程--> <boolProp name="ThreadGroup.scheduler">true</boolProp> <stringProp name="ThreadGroup.duration">120</stringProp> <!--测试持续时间--> <stringProp name="ThreadGroup.delay"></stringProp> <boolProp name="ThreadGroup.same_user_on_next_iteration">false</boolProp> </SetupThreadGroup>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> <collectionProp name="Arguments.arguments"/> </elementProp> <stringProp name="HTTPSampler.domain">x.x.x.x</stringProp> <!--请求地址--> <stringProp name="HTTPSampler.port">xxxx</stringProp> <!--请求端口--> <stringProp name="HTTPSampler.protocol">http</stringProp> <!--请求协议--> <stringProp name="HTTPSampler.contentEncoding"></stringProp> <stringProp name="HTTPSampler.path">/xxxx</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.connect_timeout"></stringProp> <stringProp name="HTTPSampler.response_timeout"></stringProp> </HTTPSamplerProxy>
-
如果需要测试存储服务或其他下载服务,可以选中当前线程组,右键添加后置处理程序-》BeanShell PostProcessor,加入以下脚本:
import java.io.*; import java.util.UUID; //获取上个请求的返回数据 byte[] result = prev.getResponseData(); //要下载到什么地方 String file_name = "\\apps\\temp\\downloads\\"+UUID.randomUUID().toString()+".jpg"; File file = new File(file_name); FileOutputStream out = new FileOutputStream(file); out.write(result); out.close();
或直接在jmx文件中与”ResultCollector“平级的层加入:
<BeanShellPostProcessor guiclass="TestBeanGUI" testclass="BeanShellPostProcessor" testname="BeanShell PostProcessor" enabled="true"> <stringProp name="filename"></stringProp> <stringProp name="parameters"></stringProp> <boolProp name="resetInterpreter">false</boolProp> <stringProp name="script">import java.io.*; import java.util.UUID; //获取上个请求的返回数据 byte[] result = prev.getResponseData(); //要下载到什么地方 String file_name = "\\apps\\temp\\downloads\\"+UUID.randomUUID().toString()+".jpg"; File file = new File(file_name); FileOutputStream out = new FileOutputStream(file); out.write(result); out.close();</stringProp> </BeanShellPostProcessor>
五、常见的性能瓶颈
- 网络带宽(尤其是压测请求的请求体或响应体较大的时候尤为明显)
- 磁盘IO(与之相关的是nginx、程序的日志级别)
- JVM参数
- 网络参数(/etc/ sysctl.conf)