开源压力测试工具JMeter使用介绍

最近需要对改造的redis缓存接口做压力测试,使用了开源压力测试工具JMeter,分享一下自己的使用经验,希望能对需要进行压力测试的开发同学有所帮助。

JMeter介绍

JMeter是Apache软件基金会下的一款开源压力测试工具,官方网址是:http://jmeter.apache.org/ 。JMeter可以测试静态、动态资源的性能,这些资源包括文件、Servlets 、Perl脚本、Java对象、数据库、FTP服务器等,并生成图形报告。JMeter使用Java开发,既支持可视化界面操作,也支持命令行操作。

Java请求测试(界面操作)

由于需要对改造的redis缓存接口测试,因此使用了JMeter的Java请求测试,安装和使用步骤如下所示(以Windows操作系统为例,并默认已安装、配置Java运行环境)。

  1. 从官网上下载JMeter并解压,例如解压至C:\apache-jmeter-2.9。
  2. 配置JMeter环境变量,增加变量JMETER_HOME,值为“C:\apache-jmeter-2.9”,修改变量CLASSPATH,增加“%JMETER_HOME%\lib\ext\ApacheJMeter_core.jar;% JMETER_HOME%\lib\jorphan.jar;%JMETER_HOME%\lib\logkit-1.2.jar;”
  3. 执行JMeter目录下的bin\jmeter.bat,显示JMeter界面说明安装成功。
    120114940
  4. 新建Java工程,在该工程中,引入JMeter目录下lib中的jar包,继承JMeter的AbstractJavaSamplerClient类,在子类中重写setupTest、teardownTest、getDefaultParameters、runTest方法,实现对缓存接口的get、set、hGet、hSet方法进行测试,子类代码如下所示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    package com.sohu.cms.test;
    import org.apache.jmeter.config.Arguments;
    import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
    import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
    import org.apache.jmeter.samplers.SampleResult;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import com.sohu.cms.datacache.DataCache;
    import com.sohu.cms.datacache.DataCacheManager;
    /**
    * 继承jmeter的AbstractJavaSamplerClient类,
    * 重写setupTest、teardownTest、getDefaultParameters、runTest方法
    * 对缓存接口的get、set、hGet、hSet方法进行测试
    * @author wang
    *
    */
    public class TestDataCacheClient extends AbstractJavaSamplerClient{
    private static long start = 0;
    private static long end = 0;

    private static DataCacheManager dataCacheManager;
    private static DataCache dataCache;
    private static int size = 8192;
    private static String method = "get";
    private static Object key = "TEST_KEY";
    private static Object field = "TEST_FIELD";
    private static Object value = new Byte[size];

    //此处初始化我们改造的redis缓存接口dataCache
    static {
    ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
    dataCacheManager = (DataCacheManager) context.getBean("dataCacheManager");
    dataCache = dataCacheManager.getObjectDataCache();
    }
    /**
    * 测试开始
    */
    public void setupTest(JavaSamplerContext arg0) {
    start = System.currentTimeMillis();
    }
    /**
    * 测试结束
    */
    public void teardownTest(JavaSamplerContext arg0) {
    end = System.currentTimeMillis();
    System.out.println("[method=" + method + "] [size=" + size + "] [time:" + (end - start) / 1000);
    }
    /**
    * 添加参数默认值,参数默认值会在界面中显示
    */
    public Arguments getDefaultParameters() {
    //添加两个参数,
    //size为测试value值的字节大小,默认为8192字节
    //method为需要进行测试的缓存接口方法,默认为get
    Arguments args = new Arguments();
    args.addArgument("size", "8192");
    args.addArgument("method","get");
    return args;
    }
    /**
    * 测试
    */
    public SampleResult runTest(JavaSamplerContext arg0) {
    //获取界面或测试计划配置文件(jmx)中传入的参数
    if (arg0.getParameter("method") != null) method = arg0.getParameter("method");
    if (arg0.getIntParameter("size") > 0) size = arg0.getIntParameter("size");
    value = new Byte[size];

    SampleResult sr = new SampleResult();
    try {
    boolean result = true;
    //根据传入的method参数测试相应的缓存接口方法
    if (method.equals("get")){
    //测试用例的核心逻辑
    //测试用例开始
    sr.sampleStart();
    //测试用例执行
    result = get();
    //测试用例结果
    sr.setSuccessful(result);
    //测试用例结束
    sr.sampleEnd();
    } else if (method.equals("set")) {
    sr.sampleStart();
    result = set();
    sr.setSuccessful(result);
    sr.sampleEnd();
    } else if (method.equals("hGet")) {
    sr.sampleStart();
    result = hGet();
    sr.setSuccessful(result);
    sr.sampleEnd();
    } else if (method.equals("hSet")) {
    sr.sampleStart();
    result = hSet();
    sr.setSuccessful(result);
    sr.sampleEnd();
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    return sr;
    }

    /**
    * 缓存接口get方法测试用例
    * @return
    */
    public boolean get() {
    boolean result = true;
    try {
    dataCache.get(key);
    } catch (Exception e) {
    System.out.println(e);
    result = false;
    }
    return result;
    }

    /**
    * 缓存接口set方法测试用例
    * @return
    */
    public boolean set() {
    boolean result = true;
    try {
    dataCache.put(key, value);
    } catch (Exception e) {
    System.out.println(e);
    result = false;
    }
    return result;
    }

    /**
    * 缓存接口hGet方法测试用例
    * @return
    */
    public boolean hGet() {
    boolean result = true;
    try {
    dataCache.hGet(key, field);
    } catch (Exception e) {
    System.out.println(e);
    result = false;
    }
    return result;
    }

    /**
    * 缓存接口hSet方法测试用例
    * @return
    */
    public boolean hSet() {
    boolean result = true;
    try {
    result = dataCache.hSet(key, field, value);
    } catch (Exception e) {
    System.out.println(e);
    result = false;
    }
    return result;
    }
    }
  5. 将工程以jar包形式导出,置于JMeter目录lib\ext下,并将工程依赖的jar包置于JMeter目录lib下,启动JMeter。右键“测试计划”添加“线程组”。右键“线程组”添加“Java请求”,右键“Java请求”添加“聚合报告”。
    120427371
    120429563
    120431779

  6. 在“Java请求”中选择所写的测试类,并可以设置相关参数,在“线程组”中可以设置并发线程数和循环次数,点击上方“启动”按钮开始测试,测试完成后,可以在“聚合报告”中查看到测试结果。
    120525177
    120527349
    120529448

Java请求测试(命令行操作)

JMeter也支持命令行操作,在服务器上进行测试时,我们就采用了这种方式。一个简单的JMeter命令行操作如下所示(在JMeter目录bin下执行)。

Windows下: JMeter-n –t test.jmx -l result.jtl
Linux下: ./jmeter.sh -n –t test.jmx -l result.jtl

其中-n 表示命令行执行,test.jmx为测试计划配置文件,result.jtl为测试结果。
可在界面中进行测试计划配置,然后保存便可生成jmx格式文件,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="2.4" jmeter="2.9 r1437961">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="测试计划" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="线程组" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="循环控制器" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<!-- 循环次数 -->
<stringProp name="LoopController.loops">100</stringProp>
</elementProp>
<!-- 并发线程数 -->
<stringProp name="ThreadGroup.num_threads">100</stringProp>
<stringProp name="ThreadGroup.ramp_time">100</stringProp>
<longProp name="ThreadGroup.start_time">1376103961000</longProp>
<longProp name="ThreadGroup.end_time">1376103961000</longProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
<hashTree>
<JavaSampler guiclass="JavaTestSamplerGui" testclass="JavaSampler" testname="Java请求" enabled="true">
<elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true">
<!-- 自定义参数 -->
<collectionProp name="Arguments.arguments">
<elementProp name="size" elementType="Argument">
<stringProp name="Argument.name">size</stringProp>
<stringProp name="Argument.value">8192</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="method" elementType="Argument">
<stringProp name="Argument.name">method</stringProp>
<stringProp name="Argument.value">set</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="classname">com.sohu.cms.test.TestDataCacheClient</stringProp>
</JavaSampler>
<hashTree>
<ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="聚合报告" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
`true`
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>false</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>

result.ftl为按行输出的测试执行结果,如下所示,可在界面操作的聚合报告导入该文件从而生成聚合报告。


1376040889436,48,Java请求,,,线程组1-40,,true,0,0
1376040889792,9,Java请求,,,线程组1-7,,true,0,0
1376040889526,167,Java请求,,,线程组1-57,,true,0,0
1376040889791,10,Java请求,,,线程组 1-54,,true,0,0