Stress Testing Tool - Jmeter

 

简介

Jmeter, 由apache开源的一个非常成熟的压测工具

安装

在mac上,直接用homebrew安装即可:

brew install jmeter

安装完成后,运行jmeter命令即可启动jmeter可视化操作界面。使用jmeter -t your_script.jmx可以指定脚本。
如果不想启动命令行,想直接执行脚本,并且输出结果,可以使用jmeter -n -t your_script.jmx -l result.jtl -e -o result

使用

Test Plan

一个新建的jmeter脚本文件,最外层为test plan,可以添加如下组件:

  • Threads: 添加测试用的线程组
  • Config Element: 添加一些整体配置,如http请求头,dns缓存等
  • Listener: 一系列监听结果的可视化,如result tree, summary report等,包含错误率、吞吐、时延等数据
  • Timer: 一些计时器
  • Pre Processer: 在执行之前的操作
  • Post Processer: 在执行之后的操作
  • Assertion: 断言

Thread Group

一个执行的线程组,根据里面的配置可以实现并发请求:

  • Number of Threads(users): 模拟多个线程(用户)同时发起请求,即并发数
  • Ramp-up period(seconds): 启动时间
  • Loop Count: 选择循环次数 常用的往thread group中添加的组件:
  • Sampler
    • BeanShell Sampler: java脚本。例如:
        // 通过脚本设置一个变量id,值为uuid
        import java.util.UUID;
        UUID uuid = UUID.randomUUID();
        vars.put("id",(uuid.toString()).toLowerCase().replaceAll("-","")); 
      
    • HTTP Request: 发送http请求
  • Logic Controller: 添加一些逻辑操作,比如if-else,用来控制组件的执行流程

HTTP Request

一个实际执行的请求,通常可以对他的入参出参进行一些配置

  • Config Element:
    • HTTP Header Manager: 处理请求头等参数
  • Pre Proccessors
    • User Parameters: 用户参数模板
  • Post Processors
    • JSON Extractor: HTTP返回包JSON字段解析

请求的数据也可以使用之前预先生成的变量,例如入参为:

{
    "id": "${id}",
    "name": "test"
}

常用

  1. 添加的Thread Group除了有普通的Thread Group,还有另外两种常用的:
    • setUp Thread Group: 在普通的线程组之前运行,通常用于进行一些前置的初始化流程。比如数据准备、token生成等等
    • tearDown Thread Group: 在普通的线程组之后运行,通常用于一些收尾操作,比如数据库清理、日志清理等
  2. 不同线程组中的变量是无法互通的,如果想要互通的话,需要通过脚本设置全局变量。例如组A通过http请求获得的返回包,通过JSON Extractor获得了组内的变量token,那么为了让这个变量成为全局可用,可以:

    (1) 为http请求添加Post Processors => BeanShell PostProcessor,指定Parameters${token},并在脚本中将此值通过函数__setProperty赋予给全局变量globalToken:

    ${__setProperty(globalToken, ${token}, )} 
    

    (2) 设置完之后,在其他的线程组想使用此全局变量时,就可以使用${__P(globalToken)}获取到对应的值

脚本开发

如果每次的脚本开发都是通过GUI的形式去开发,那么会面临几个问题:

  • 每次一个微小的操作,都可能导致脚本发生改变,自己可能根本不知道改动了什么
  • 对于历史的脚本难以做到归档
  • 对当前的脚本需要仔细保存,一旦丢失,很难重新制作一个一模一样的脚本

为了彻底解决这个问题,最好的方式自然是使用jmeter自带的api来进行开发。我们可以保证同一份每次运行都能产生相同的脚本,并且通过git等管理工具,也能轻松重现历史版本的脚本。
官方的api可在这里查询: https://jmeter.apache.org/api/index.html。 maven的依赖参考如下:

<dependencies>
  <dependency>
    <groupId>org.apache.jmeter</groupId>
    <artifactId>ApacheJMeter_config</artifactId>
    <version>5.5</version>
  </dependency>
  <dependency>
    <groupId>org.apache.jmeter</groupId>
    <artifactId>ApacheJMeter_java</artifactId>
    <version>5.5</version>
  </dependency>
  <dependency>
    <groupId>org.apache.jmeter</groupId>
    <artifactId>ApacheJMeter_http</artifactId>
    <version>5.5</version>
  </dependency>
  <dependency>
    <groupId>org.apache.jmeter</groupId>
    <artifactId>ApacheJMeter_core</artifactId>
    <version>5.5</version>
  </dependency>
</dependencies>

仅生成脚本

public static void main( String[] args ) throws Exception {
  // jmeter binary所在路径 我这边使用homebrew安装
  String jmeterHomePath = "/usr/local/Cellar/jmeter/5.5/libexec";
  JMeterUtils.setJMeterHome(jmeterHomePath);
  JMeterUtils.loadJMeterProperties(jmeterHomePath + "/bin/jmeter.properties");
  JMeterUtils.initLocale();

  // 测试计划
  TestPlan testPlan = new TestPlan();
  testPlan.setName("Test Plan");
  testPlan.setTearDownOnShutdown(true);
  testPlan.setProperty(TestElement.TEST_CLASS, TestPlan.class.getName());
  testPlan.setProperty(TestElement.GUI_CLASS, TestPlanGui.class.getName());
  testPlan.setUserDefinedVariables((Arguments) new ArgumentsPanel().createTestElement());

  // 在测试计划下,添加你的线程组等等
  // ...
  
  // 涵盖整个测试流程的哈希树
  ListedHashTree tree = new ListedHashTree();
  // 将测试计划加入树中
  // 想表达树的父子结构,用这种写法: tree.add(testPlan, someThreadGroup);
  tree.add(testPlan);

  // 生成脚本 test.jmx
  SaveService.saveTree(tree, new FileOutputStream("/path/to/save/your/jmeter/script/test.jmx"));
}

直接执行

如果想直接在程序内布置好结构树,不生成脚本文件,直接执行,可以这么做:

public static void main( String[] args ) throws Exception {
  // 设置jmeter路径和构建测试树同上 省略

  // 初始化jmeter engine
  StandardJMeterEngine jme = new StandardJMeterEngine();

  // 不生成脚本 而是直接执行
  jme.configure(tree);
  jme.run();
}

参考