Jmeter基本使用与常见性能瓶颈

一、什么是Jmeter

官网链接

Apache JMeter 是 Apache 组织基于 Java 开发的压力测试工具,用于对软件做压力测试。
JMeter 最初被设计用于 Web 应用测试,但后来扩展到了其他测试领域,可用于测试静态和动态资源,如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库和 FTP 服务器等等。JMeter 可对服务器、网络或对象模拟巨大的负载, 在不同压力类别下测试它们的强度和分析整体性能。另外,JMeter 能够对应用程序做功能/回归测试,通过创建带有断言的脚本来验证程序是否返回了期望结果。为了最大限度的灵活性,JMeter 允许使用正则表达式创建断言。

二、Jmeter中的相关概念

  1. 线程:一个线程可以理解为一个虚拟用户。比如10个线程表示有10个用户不间断发起请求,也可以理解为并发数
  2. 线程组:测试的起点。一个测试计划必须包含至少一个线程组,其余所有组件都必须在线程组下
  3. 监听器:用来查看测试过程中的各项指标
  4. 取样器(Sample)是性能测试中向服务器发送请求,记录响应信息,记录响应时间的最小单元。JMeter 原生支持多种不同的sampler ,如 HTTP Request Sampler 、 FTP Request Sample 、TCP Request Sample 、JDBC Request Sampler 等,每一种不同类型的 sampler 可以根据设置的参数向服务器发出不同类型的请求。(实际上就是配置请求地址和请求参数的地方
  5. 定时器(Timer)用于操作之间设置等待时间,等待时间是性能测试中常用的控制客户端QPS的手端。类似于LoadRunner里面的“思考时间”。JMeter 定义了Bean Shell Timer、Constant Throughput Timer、固定定时器等不同类型的Timer。

三、压力测试过程

本小节以单一请求为例,描述压力测试设置请求参数、查看测试结果的过程

  1. 从官网下载jmeter,解压缩
  2. Windows下打开bin/jmeter.cmd
  3. 新建测试计划与线程组。打开后默认已建立一个测试计划,右击测试计划根据需要新建线程组,一般选择“setUp Thread Group(常规线程组)”或“Stepping Thread Group(阶梯测试线程组)”
  4. 右击线程组根据需要新建取样器(设置请求参数的地方),一般我们是新建HTTP Request
  5. 右击线程组,根据需要查看的指标新建监听器,一般结果树(view Result Tree)和聚合报告(Summary Report)是必需的,前者可以看到jmeter发出的每个请求的响应情况,后者可以在生成实时汇总报告
#线程组参数说明
- 线程数: 同时发起的请求数。如线程数为10表示用10个线程模拟10个用户发起请求
- Ramp-Up时间: 多长时间内启动完全部线程
- 循环次数:每个线程循环请求的次数,设置为永远则所有线程持续不间断请求
- 持续时间: 测试请求的时间
- 启动延迟: 单一脚本时用不上。此参数表示当有多个jmeter脚本时,间隔多长时间启动下一脚本
  1. 因为jmeter图形化界面本身会占用一定资源,为了最大化资源利用效率,jmeter官方也推荐在命令行下进行压力测试,而仅在图形界面设置参数与查看最终结果,因此以上设置完成之后需要保存为jmx文件
  2. 将jmx文件与jmeter压缩包一起上传到linux服务器或其他有命令行的地方,执行以下命令开始压测
jmeter -n -t ./xxx.jmx -l ../xxx.jtl -e -o ../xxx
 #参数说明:
 - -n 表示以命令行执行脚本
 - -t 指明测试脚本文件的位置(测试脚本文件为jmx文件)
 - -l 指明测试结果保存的位置(测试结果文件为jtl文件)
 - -e -o 表示同时生成网页报告以及网页文件存放的目录,该目录应为空或不存在
  1. 将测试结果文件(jtl文件)拿出来,导入到jmeter图形界面中,查看测试结果;或将网页文件拿出来在网页上查看测试结果

四、测试结果查看

  1. 测试过程中的实时查看:

jmeter压测rpc 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个
  1. 测试结束后将结果文件(jtl文件)导入到jmeter图形界面:
    结果树:
  2. jmeter压测rpc Jmeter压测性能瓶颈问题_响应时间_02

  3. 结果树可以看到每个请求的响应情况
    聚合报告:

jmeter压测rpc Jmeter压测性能瓶颈问题_运维_03

聚合报告可以看到发起的总请求数、平均响应时间、响应时间的中位数、90%的请求在多长时间内完成响应、95%的请求在多长时间内完成响应、99%的请求在多长时间内完成响应、响应时间的最大值、响应时间的最小值、异常请求所占的比例、吞吐量(TPS)以及发送、接受数据的速度

Tips

  1. 如果调整参数每次都在图形界面进行,对于我们在命令行进行测试十分不方便,我们可以直接修改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>
  1. 如果需要测试存储服务或其他下载服务,可以选中当前线程组,右键添加后置处理程序-》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)