一、Apache JMeter

Apache JMeter是一个开源软件,100%纯Java应用程序,旨在负载测试功能行为和衡量性能。它最初是为测试Web应用程序而设计的,但后来扩展到了其他测试功能。

官网:https://jmeter.apache.org/

本文将用Jmeter对tomcat下的web应用进行压力测试。

二、使用Jmeter

1、在官网下载应用

2、执行bin目录下的jmeter.bat,打开GUI

3、创建 Plan -> 添加 Thread Group -> 添加 Http Request -> 执行测试计划 start -> 保存为jmx文件

4、创建Plan,添加线程组,配置线程数和持续时间(若配置持续10秒的200并发,则添加2000个线程,时间设置为10秒)

4、添加 Http Request,设置被测试的接口

5、若使用GUI界面进行测试,则可以根据需要添加lisener,如View Results Tree、Summary Report等(官方不推荐)

6、保存为jmx测试文件

7、命令行执行测试计划

# 生成jtl文件
jmeter -n -t D:\software\apache-jmeter-5.3\test_file\test1.jmx -l D:\software\apache-jmeter-5.3\test_file\v2\test.jtl

# jtl生成测试报告
jmeter -g D:\software\apache-jmeter-5.3\test_file\v1\test.jtl -e -o D:\software\apache-jmeter-5.3\test_file\v1\html

# 直接生成测试报告
jmeter -n -t D:\software\apache-jmeter-5.3\test_file\test1.jmx -l D:\software\apache-jmeter-5.3\test_file\v2\test.jtl -e -o D:\software\apache-jmeter-5.3\test_file\v2\html

如果命令报错:

Error generating the report: org.apache.jmeter.report.dashboard.GenerationException: Cannot assign “${jmeter.reportgenerator.apdex_satisfied_threshold}” to property “set_satisfied_threshold” (mapped as “setSatisfiedThreshold”), skip it … end of run

则修改user.perperties配置

jmeter.reportgenerator.apdex_satisfied_threshold=1500
jmeter.reportgenerator.apdex_tolerated_threshold=1500

然后在所有命令后指定配置文件

-p D:\software\apache-jmeter-5.3\bin\user.properties

三、批量接口测试

下面是一个自动化测试脚本,可以对多个接口进行不同并发的测试、减少人为干预因素


for %%i in (100,200,500,1000) do (
    echo %%i concurrent start
    
    md E:\temp\jmeter_test\c%%i\api1\html

    md E:\temp\jmeter_test\c%%i\api2\html

    md E:\temp\jmeter_test\c%%i\api3\html

    md E:\temp\jmeter_test\c%%i\api4\html
    
    ping -n 120 localhost >nul

    call "D:\software\apache-jmeter-5.3\bin\jmeter" -n -t E:\temp\jmeter_test\plan\api1\test%%i.jmx -l E:\temp\jmeter_test\c%%i\api1\test.jtl -e -o E:\temp\jmeter_test\c%%i\api1\html -p D:\software\apache-jmeter-5.3\bin\user.properties

    echo %%iapi1 test finish.

    ping -n 60 localhost >nul

    call "D:\software\apache-jmeter-5.3\bin\jmeter" -n -t E:\temp\jmeter_test\plan\api2\test%%i.jmx -l E:\temp\jmeter_test\c%%i\api2\test.jtl -e -o E:\temp\jmeter_test\c%%i\api2\html -p D:\software\apache-jmeter-5.3\bin\user.properties

    echo %%iapi2 test finish.

    ping -n 60 localhost >nul

    call "D:\software\apache-jmeter-5.3\bin\jmeter" -n -t E:\temp\jmeter_test\plan\api3\test%%i.jmx -l E:\temp\jmeter_test\c%%i\api3\test.jtl -e -o E:\temp\jmeter_test\c%%i\api3\html -p D:\software\apache-jmeter-5.3\bin\user.properties

    echo %%iapi3 test finish.

    ping -n 60 localhost >nul

    call "D:\software\apache-jmeter-5.3\bin\jmeter" -n -t E:\temp\jmeter_test\plan\api4\test%%i.jmx -l E:\temp\jmeter_test\c%%i\api4\test.jtl -e -o E:\temp\jmeter_test\c%%i\api4\html -p D:\software\apache-jmeter-5.3\bin\user.properties

    echo %%iapi4 test finish.
)

echo "all finish!"

pause

得到测试报告后,可以执行下面程序来获取到所有的测试结果,这里的结果为markdown的表格


public class JmeterFileAnalysis {


    public static final String PATH = "E:\\temp\\jmeter_test\\result";
    // 数据行
    public static final String REGEX_STATISTICS = "statisticsTable";

    public static void main(String[] args) {
        List<File> list = new ArrayList<>();
        getMatchFiles(new File(PATH), list);
        for (File file : list) {
            System.out.println(file.getAbsolutePath());

            try (FileReader reader = new FileReader(file);
                 BufferedReader br = new BufferedReader(reader)) {
                String line;
                StringBuffer sb = null;
                String tableName = null;
                while ((line = br.readLine()) != null) {
                    if (line.contains(REGEX_STATISTICS)) {// 表名
//                        System.out.println(line);
                        System.out.println(resovleLine(line));
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static String getMatchContent(String content, String regex) {
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(content);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return "";

    }

    /**
     * 获取所有包含数据的文件路径
     * @param directory
     * @param list
     */
    public static void getMatchFiles(File directory, List<File> list) {
        File[] files = directory.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                getMatchFiles(file, list);
            } else {
                if (file.getName().contains("dashboard.js")) {
                    list.add(file);
                }
            }
        }
    }

    public static String m2(String num) {
        BigDecimal bg = new BigDecimal(num);
        return bg.setScale(2, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 处理包含数据的行、转化为md表格
     * @param line
     * @return
     */
    public static String resovleLine(String line) {
        String json = line.replace(" createTable($(\"#statisticsTable\"), ", "").replace(", function(index, item){", "");
        JSONObject object = JSON.parseObject(json);
        JSONArray titles = object.getJSONArray("titles");
        JSONArray data = object.getJSONObject("overall").getJSONArray("data");
//        System.out.println(titles);
//        System.out.println(data);
        StringBuffer sb = new StringBuffer();
        titles.forEach(e -> {
            sb.append(" | ").append(e.toString());
        });
        sb.append("| \r\n");
        sb.append("| --------- | ----------- | --------- | --------- | --------- | --------- | --------- | --------- | ---- | ---- | ---- | ---- | ---- | ---- |\r\n");
        data.forEach(e -> {
            if (e.toString().contains(".")) {

            }
            String temp = e.toString().contains(".") ? m2(e.toString()) : e.toString();
            sb.append(" | ").append(temp);
        });
        sb.append("| \r\n");
        return sb.toString();
    }

}

结果示例:

E:\temp\jmeter_test\result\c100\api1\html\content\js\dashboard.js
| Label | #Samples | KO | Error % | Average | Min | Max | Median | 90th pct | 95th pct | 99th pct | Transactions/s | Received | Sent|
| ——— | ———– | ——— | ——— | ——— | ——— | ——— | ——— | —- | —- | —- | —- | —- | —- |
| Total | 2000 | 0 | 0.00 | 512.77 | 5 | 5665 | 15.00 | 2074.00 | 3338.45 | 4911.91 | 100.49 | 271.34 | 115.31|

E:\temp\jmeter_test\result\c100\api2\html\content\js\dashboard.js
| Label | #Samples | KO | Error % | Average | Min | Max | Median | 90th pct | 95th pct | 99th pct | Transactions/s | Received | Sent|
| ——— | ———– | ——— | ——— | ——— | ——— | ——— | ——— | —- | —- | —- | —- | —- | —- |
| Total | 2000 | 0 | 0.00 | 9.12 | 3 | 81 | 6.00 | 18.00 | 27.00 | 51.99 | 100.35 | 98.48 | 62.62|

这样就可以快速得到所有的测试结果,方便比较

四、压测过程中程序的优化方向

1、tomcat内存优化(JVM参数)、线程优化(连接数、协议)

2、数据库连接池优化

3、mysql连接数、索引等(使用show processlist查看线程数)

五、使用JVisualVM监控服务器状态

JVisualVM是jdk自带的可视化监控工具,压测过程中我们可以通过它来监控程序的状态。

使用步骤:

  • 1、修改tomcat的catalina.sh
CATALINA_OPTS="-Djava.rmi.server.hostname=192.168.18.132 -Dcom.sun.management.jmxremote.port=1100 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"

注:
-Dcom.sun.management.jmxremote.port :1100 这个是配置远程 connection 的端口号的,要确定这个端口没有被占用
-Dcom.sun.management.jmxremote.ssl=false   指定了 JMX 是否启用 ssl
-Dcom.sun.management.jmxremote.authenticate=false   指定了JMX 是否启用鉴权(需要用户名,密码鉴权)
前面是固定配置,是 JMX 的远程服务权限的
-Djava.rmi.server.hostname :这个是配置 server 的 IP 的
  • 2、打开JVisualVM,添加对应远程连接