压力测试实践一:JMeter + JProfiler 入门

2,854 阅读7分钟

这段时间工作中本人对项目进行了一次压测,用到了不少常见的压测技术,和一些典型的场景,这里简单分享下。看完你可以解答如下一些问题:

  • JMeter 如何发送有签名验签或者是加密的复杂请求?
  • JMeter 如何控制线程步进式增长?
  • JMeter 如何监控服务器负载?
  • 如何监控被压测 JVM 状态?线程状态?

JMeter

JMeter 添加 Java Request Sampler

绝大多数后端接口形式的请求报文都不是明文,大多都需要进行签名验签,我们项目也不例外。这就是遇到的第一个问题。好在 JMeter 也提供了自定义扩展功能,Java Request,用来 Java 代码自定义发送压测请求的功能。

首先要做的是创建一个单独的项目,目的是 签名验签并发送 HTTP 请求,想要与 JMeter 相结合还必须实现 org.apache.jmeter.protocol.java.sampler.JavaSamplerClient 接口或者继承 org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient,创建好以后假如在 JMeter 界面上配置了当前创建类,JMeter 就会反过来调用你写的方法(钩子、SPI、插件)。

这两个类是在 JMeter 项目有引入,但是自己项目想要访问,可以用 Maven 引入。如果自己创建的项目不是 Maven,可以在 JMeter_HOME/lib/ext 文件夹下找到。所以在这里 Maven 的依赖范围我们选择到了 <scope>provided</scope>

<dependency>
    <groupId>org.apache.jmeter</groupId>
    <artifactId>ApacheJMeter_java</artifactId>
    <version>5.3</version>
    <scope>provided</scope>
</dependency>

代码示例如下:

public class TestJavaRequestSamplerClient implements JavaSamplerClient {
    // 做一些资源的初始化工作
    public void setupTest(JavaSamplerContext context) {}

    // 需要的默认参数,会展示在界面上
    public Arguments getDefaultParameters() {
        Arguments args = new Arguments();
        args.addArgument("url", "");
        args.addArgument("type", "");
        return args;
    }

    // 需要执行的业务方法
    public SampleResult runTest(JavaSamplerContext context) {
        // 用于记录执行结果的状态
        SampleResult sampleResult = new SampleResult();
        // 获取 JMeter 中输入的用户参数
        String url = context.getParameter("url", "默认值");
        String type = context.getParameter("type", "默认值");
        
        // 标记开始
        sampleResult.sampleStart();
        boolean flag = this.doRequest();
        sampleResult.setSuccessful(flag);
        // 标记暂停
        // sampleResult.samplePause();
        // 标记重启
        // sampleResult.sampleResume();
        // 标记结束
        sampleResult.sampleEnd();
        
        return sampleResult;
    }

    private boolean doRequest() {
        // 组装报文
        // 进行签名
        // 发送 HTTP 请求
        // 解析响应
        return false;
    }

    //
    public void teardownTest(JavaSamplerContext context) {}
}

接下来需要把这个项目打成 .jar 形式并拷贝到 {JMeter_HOME}\lib\ext 下。接着在 JMeter 界面上添加 Java Request 类型的 采样器 如下,下拉列表中看到 TestJavaRequestSamplerClient 类了

这里需要重点注意!添加的是 采样器下面的 Java 请求

Sampler > Java Request 而不是 Config Element > Java Request Defaults

这两个搞错会导致发不出去请求

image.png

特别注意几点:

  1. Maven 项目的编译等级不要选太高,建议 1.5
  2. 如果创建的项目还有除 ApacheJMeter_java 以外的依赖,也必须一同拷贝到 {JMeter_HOME}\lib\ext
  3. jar 文件放置好以后,重启 JMeter 才能识别

JMeter 添加一些常用插件

安装插件省略

  • Stepping Thread Group(步进线程组)

这里已经被标记为弃用,新的名字为 Concurrency Thread Group,不过用法也是大同小异。这个插件能很灵活配置线程的启动方式,目的是维护并发级别。

image.png

下图是一个本人使用实际压测配置

image.png

  • Aggregate Report(监听器,聚合报告)

必须加上,统计 TPS 用

image.png

- Label:每个 JMeter 的 element(例如 HTTP Request)都有一个 Name 属性,这里显示的就是 Name 属性的值
- #Samples:此次测试中共发出了多少请求
- Average:平均响应时间——默认情况下是单个 Request 的平均响应时间
- Median:中位数,也就是 50% 用户的响应时间
- 90% Line:90% 用户的响应时间
- 95% Line:95% 用户的响应时间
- 99% Line:99% 用户的响应时间
- Min:最小响应时间
- Max:最大响应时间
- Error%:本次测试中出现错误的请求的数量/请求的总数
- Throughput:吞吐量——默认情况下表示每秒完成的请求数(Request per Second),可以认为是 TPS
- KB/Sec:每秒从服务器端接收到的数据量
  • Transactions per Second(每秒事务处理量)

image.png

  • Response Times Over Time(响应时间变化趋势图)

image.png

  • PerfMon Metrics Collector(性能指标收集器)

假如要收集被压测服务器资源使用信息,可以用这个插件。首先需要在被压测服务器上启动 ServerAgent 程序,然后在 JMeter 界面配置目标机器以及监控指标,官方文档

image.png

下面是一个监控结果

image.png

!注意!在 Non-Gui 模式下,使用该插件时必须单独配置 FileName 属性,否则用 -l 参数生成的结果文件导入无法显示监控指标曲线 资料:stackoverflow.com/questions/5…

使用 NON GUI mode 执行脚本

由于 JMeter 界面运行时会损耗一部分的性能,会对压测结果造成偏差。所以一般在压测中使用 non-gui 模式 来运行 JMeter 脚本

在 JMeter 界面上配置好测试计划后,导出为 .jmx 文件,接着使用命令运行它(假如是在另一台 linux 服务器,首先保证需要有 JMeter 程序)

# ./jmeter -n -t demo.jmx -l 结果文件.jtl

上文我们配置一个了 TestJavaRequestSamplerClient 的 Java Request 要求两个参数 url 和 type,在 JMeter non-gui 模式下使用 -J 来传递

# ./jmeter -n -t demo.jmx -Jurl='http://127.0.0.1:8080/test' -Jtype='1' -l 结果文件.jtl

!非常重要!得到 xxx.jtl 结果文件之后,可以使用 JMeter 界面上的 FileName 文件选择框中导入来查看。假如导入时,界面上还显示着上一个报告的内容,!千万要清空后,再导入,否则会发生数据错乱!

image.png

JPorfiler

配置好上文列出的 JMeter 插件和配置以后,我们已经可以拿到应用的 TPS 和所对应的服务器资源情况。但如果此时发现程序性能有问题,我们还少个观察、监控应用的功能,来分析性能瓶颈,这里我们选择了 JProfiler

方式一【简单、推荐】:使用 Attach 模式(jpenable)

在远端服务器下载好对应的 linux 版本 jprofiler(jprofiler_linux_10_1_5.tar.gz),并解压,接着

2021-09-03_101345.png

然后在本地 Jprofiler 连接即可 2021-09-03_101740.png

方拾二【较麻烦】使用配置向导(添加 jvm 启动参数模式)

image.png

选择对应 tomcat 版本(SpringBoot 应用后面会讲)

image.png

选择远程 image.png

image.png

选择立即启动,不等待界面连接 image.png

填写远程机器 ip 地址

image.png

填写远程服务器 jprofiler 地址

image.png

image.png

这一步很关键,选择的是本地的 同版本 的 tomcat 启动脚本(startup.sh)。jprofiler

image.png

指定通信端口,默认即可

image.png

到这里为止,JProfiler 修改了两个文件,需要我们手动拷贝到远程服务器上

image.png

  1. C:\Users\xxxx.jprofiler10\config.xml:这个文件 JProfile 建议拷贝到远程服务器,但是实测不需要拷贝也能连上去
  2. startup_jprofiler.sh:这个文件必须要拷贝到远程服务器。与前文选择本地 tomcat 启动文件在一个路径,是 JProfiler 基于 startup.sh 修改一个启动脚本,对比可发现最后增加了几行。如下。这个脚本也需要拷贝到远程服务器。放在待压测应用的 tomcat bin 目录下。

image.png

接着执行启动脚本,用 JProfiler 连上即可

# sh ./startup_jprofiler.sh

常见操作

JProfiler 功能非常强大,这里只是当时用到的一些简单操作,后续用到再做补充

从概览中很清楚的能看到各种监控指标,同时可以随时保存当前监控快照,供后续回顾分析 image.png

用到的最多的就是 image.png

这是一个真实的压测情况快照 image.png

很容易就看到线程大片的红色,都被 Blocked,再看下当时线程的 dump,发现都是卡在日志写入 image.png

阻塞的原因很多:1. 磁盘读写慢;2. 其他的 IO 读写影响到了文件读写。这里不展开分析。

参考资料