jmeter压力测试过程中遇到过的问题及解决方法(包括jmeter和服务程序)

1. 关于jmeter相关的问题

1.1 为什么要编写接口测试代码?

因为有些接口需要特殊格式的参数,比如需要特定的加密处理和解密处理,有的接口参数是动态生成的,有的接口参数是上一个接口返回的,所以测试接口有时没有办法直接写成固定的参数,此时就需要通过代码来实现。

1.2 如何编写代码调用接口,并提供给jmeter执行压力测试?

编写方法是参考官方提供的示例,创建测试工程,按照示例给出的规则进行接口调用编写,参数组织,最后将工程打包为jar,放到jmeter的lib/ext目录下,启动jmeter,然后就可以通过界面调用编写的测试接口了。

测试代码Demo如下:

package com.platform;

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;

public class JMeterTestDemo extends AbstractJavaSamplerClient {
	
	private SampleResult results;
    private String testStr;
    
    // 准备测试
    public void setupTest(JavaSamplerContext arg0) {
        results = new SampleResult();
        
        // 获取参数值
        testStr = arg0.getParameter("Parameter1");
        
        if(testStr!=null&&testStr.length()>0){
            results.setSamplerData(testStr);
        }
    }
    
    // 设置默认参数
    public Arguments getDefaultParameters() {
        Arguments params = new Arguments();
        params.addArgument("Parameter1", "aaa");
        return params;
    }
    
    
    @Override
    public SampleResult runTest(JavaSamplerContext arg0) {
        // TODO Auto-generated method stub
        int len = 0;
        // 设置开始时间
        results.sampleStart();
        
        len = testStr.length();
        
        // 设置结束时间
        results.sampleEnd();
        
        // 根据测试结果,设置测试结果信息
        if(len<5){
            System.out.println("String is  "+testStr);
            results.setSuccessful(false);
        }else{
            System.out.println("String is  "+testStr);
            results.setSuccessful(true);
        }
        
        // 返回结果信息
        return results;
    }
    
    public void teardownTest(JavaSamplerContext arg0) {
    	
    }
}

pom.xml 中添加的依赖以及编译打包:

<dependencies>                
        <dependency>
            <groupId>org.apache.jmeter</groupId>
            <artifactId>ApacheJMeter_core</artifactId>
            <version>5.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.jmeter</groupId>
            <artifactId>ApacheJMeter_java</artifactId>
            <version>5.0</version>
        </dependency>        
</dependencies>

<build>
        <plugins>
            <plugin>
                <groupId>com.lazerycode.jmeter</groupId>
                <artifactId>jmeter-maven-plugin</artifactId>
                <version>2.8.0</version>
                <executions>
                    <execution>
                        <id>jmeter-tests</id>
                        <goals>
                            <goal>jmeter</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
</build>

从界面选择测试接口:

1.3 如何从第一个接口调用返回值获取数据用于第二个接口的测试?

采用提取器,从返回信息中提取参数,第二个接口再从提取的参数中获取参数。

上述配置,展示了一个json提取器的配置。这里创建4个变量,每个变量有对应的json提取表达式,也有对应的默认值。 每个参数直接采用分号进行分割。

1.4 如何在linux服务器上执行压力测试?

首先部署jmeter环境,将windows环境下调试通过的脚本上传到linux环境,通过命令行执行压力测试脚本,如果测试时间长,为了避免前台启动的进程受到网络影响中断,建议采用后台服务形式启动。

nohup ./jmeter.sh -n -t tp208-5m.jmx -l test208-5m.jtl -e -o result208-5m &

执行完成后,查看结果文件即可。

1.5 如何通过压力测试文件,生成web形式的压力测试报告?

一般来说,压力测试完成的同时,就可以自动完成web形式的压力测试报告转换生成。如果因为各种原因,没有生成对应报告,可以在测试结束后,根据jtl文件生成对应的测试报告。

./jmeter -g ./test208-8.jtl -o ./result208-8

-g指定参数为jtl文件路径,-o指定为输出的web报告的目录。

1.6 如何控制压力测试结果文件的大小,避免测试完成后无法转换为web形式的压力测试报告?

这里的压力测试结果文件是指jtl文件,写入ResponseMessage的消息会记录到jtl文件中,提取接口调用的返回参数时,也会从该数据中读取。

如果压力测试中不需要从ResponseMessage提取参数,可以将该参数设置为一个非常简单的字符串,比如1或者0,这样jtl文件就会小。

有多个接口直接的调用存在参数依赖关系的时候,如果将这几个接口分为多个步骤调用,则前面的接口调用完成后,需要将返回结果写入ResponseMessage,用户参数提取。

也可以写一个单独的接口,在该接口内,实现对多个接口的顺序调用,这样,就不用依赖从ResponseMessage提取参数用于后续接口的调用,而是在接口内部实现了参数的传递,这样在返回到ResponseMessage中的数据,就可以是一个很简单的字符,这样jtl的文件可以大幅减小。

1.7 如何配置压力测试文件输出内容?

在jmeter.properties文件中,可以配置输出内容。

#---------------------------------------------------------------------------
# Results file configuration
#---------------------------------------------------------------------------

# This section helps determine how result data will be saved.
# The commented out values are the defaults.

# legitimate values: xml, csv, db.  Only xml and csv are currently supported.
#jmeter.save.saveservice.output_format=csv

# The below properties are true when field should be saved; false otherwise
#
# assertion_results_failure_message only affects CSV output
#jmeter.save.saveservice.assertion_results_failure_message=true
#
# legitimate values: none, first, all
#jmeter.save.saveservice.assertion_results=none
#
#jmeter.save.saveservice.data_type=true
#jmeter.save.saveservice.label=true
#jmeter.save.saveservice.response_code=true
# response_data is not currently supported for CSV output
#jmeter.save.saveservice.response_data=false
# Save ResponseData for failed samples
#jmeter.save.saveservice.response_data.on_error=false
#jmeter.save.saveservice.response_message=true
#jmeter.save.saveservice.successful=true
#jmeter.save.saveservice.thread_name=true
#jmeter.save.saveservice.time=true
......

1.8 linux环境下的压力测试注意事项

需要注意的地方包括:1. 禁用聚合报告和汇总报告,最后的报告根据jtl文件生成web形式的压测报告,因为聚合报告和汇总报告会生成单独的jtl文件,造成重复;2. 禁用查看结果树和调试采样器,这两个模块一般在调试阶段才会用到,压测的时候,使用会影响性能;

2. 关于服务程序性能相关的问题

服务程序性能一方面需要通过压力测试来验证服务程序的性能,也需要通过压力测试来发现服务程序的性能瓶颈,针对性能瓶颈进行优化,从而达到提升性能的目的。

2.1 如何查看压力测试过程中查看服务程序的内存分配和释放是否正常?是否存在内存泄漏?

采用java提供的命令行工具jstat。

jstat -gcutil pid 3000

可以查看老年代是否能够及时进行回收,尤其是在不经过全局垃圾回收的情况下。

需要查看的要点:

1. 全局垃圾回收次数和全局垃圾回收所占用的时间;

2. 老年代内存占用是否能过在不经过全局垃圾回收的情况下,自动完成回收;

一般来说,只要不会进行全局垃圾回收,或者全局垃圾回收次数很少,比如超过1小时进行一次,内存分配就不会有大的问题。如果出现频繁的全局垃圾回收,就需要查找具体的原因,有针对性地进行处理。

2.2 如何查看压力测试过程中查看内存中的数量最多的实例对象?

采用java提供的工具jmap -histo pid

jmap -histo 42327 | head -30

可以看到内存中对象实例数量最多以及占用内存空间最大的实例对象。 如果某个类的对象实例达到几十万个,则可能存在内存使用不当的问题。

2.3 如何查看jvm堆空间的内存分配情况?

采用java提供的工具jmap -heap pid,

jmap -heap 42327

可以看到堆内存各个区域的分配大小以及占用情况,如果内存分配不合理,或者程序中存在写法上的问题,就会出现有的区域内存占用太大的问题,从而影响整个服务程序的性能。 

2.4 如何提高数据库写入数据的效率,具有有哪些方法?

针对大批量数据需要快速写库的情况,可以采用批量写库的方式,主要有两种方式:一种是使用数据库连接的批量提交机制实现批量写库,该方式的优点是不需要特殊的sql语法支持,效率稍微差一些;另一种方式是将需要插入的数据,通过集合的形式传递到sql,在sql语句中进行拼接,该方式需要sql语法支持,不同的数据库,在语法上有一定的差异,比如mysql和oracle就不一样,如果中间还使用了shardingsphere,则对语法上的限制就更大了。

2.5 如何查看方法执行过程中各个函数调用使用的时间?

采用arthas的trace方法,通过该工具,可以看到每个函数调用的执行时间长度,这样有助于在做性能优化的时候,针对耗时最长的操作进行有针对性的优化,从而提高服务程序的整体性能。

[arthas@21948]$ trace com.platform.server.controller.ApiController isOpen
Press Q or Ctrl+C to abort.
Affect(class count: 2 , method count: 2) cost in 155 ms, listenerId: 1
`---ts=2021-10-22 11:23:34;thread_name=http-nio-8195-exec-17;id=4a;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@64aeaf29
    `---[9.9956ms] com.platform.server.controller.ApiController$$EnhancerBySpringCGLIB$$4e90c2f0:isOpen()
        `---[9.775ms] org.springframework.cglib.proxy.MethodInterceptor:intercept()
            `---[8.3941ms] com.platform.server.controller.ApiController:isOpen()
                +---[0.0204ms] com.platform.common.response.R:success() #75
                +---[0.0143ms] javax.servlet.http.HttpServletRequest:getSession() #76
                +---[0.1107ms] com.platform.server.dto.DeviceDto:getCifNo() #77
                +---[0.0188ms] javax.servlet.http.HttpSession:setAttribute() #77
                +---[0.0083ms] com.platform.server.dto.DeviceDto:getDeviceUuid() #79
                +---[0.0066ms] com.platform.server.dto.DeviceDto:getCifNo() #80
                +---[0.0342ms] com.platform.server.dto.DeviceDto:toString() #81
                +---[0.3729ms] org.slf4j.Logger:info() #81
                +---[7.1051ms] com.platform.service.ShldDeviceService:selectByDuuidAndCNo() #82
                +---[0.0127ms] com.platform.common.response.R:put() #89
                `---[0.4451ms] org.slf4j.Logger:info() #90

可以看出方法中各个调用所花费的时间,找出花费时间最多的方法调用,确定性能瓶颈,有针对性地进行优化,对提高性能有很好的帮助。

2.6 如何提高程序的响应速度?

提高程序的响应速度的一个主要方法就是,将能够异步处理的业务使用异步处理方式,比如记录日志,也就是在主要业务完成后,就返回结果给前端,而不用等待所有的任务都完成。

2.7 数据库连接被用完后程序将是怎样的?

在数据库连接被占用完成的以后,在需要数据库操作的地方,将出现阻塞,因为每个数据库操作,都需要获取一个数据库连接,在数据库连接上执行数据库操作,获取数据库连接时,将会出现被阻塞的情况,导致数据库操作无法完成。

2.8 如何调试修改了多个关联功能的程序?

如果一次性新增或者修改了多个关联性功能,如果调试时出现异常,常规性测试验证无法查找问题原因时,可以注释掉全部新增和修改的功能,然后通过逐个功能添加的方式进行验证,查看是哪个功能变化引起的。

2.9 如何在多线程状态下确保只会创建一个线程?

通过静态全局变量和锁机制同时使用,可以确保多线程环境下只创建一个线程对象。代码示例如下:

// 这个变量应该是静态变量
private static volatile boolean threadStarted = false;
private static ReentrantLock lock = new ReentrantLock();


// 如果线程没有启动,就启动异步线程
// 只有第一次,才需要创建日志线程
if(threadStarted == false)
{
	boolean block = lock.tryLock();

	if(block)
	{
		batchInsertLogService = new BatchInsertLogService();
		
		// 执行相关初始化及业务
		
		// 创建成功后,更新状态
		threadStarted = true;

		// 执行完成,释放锁
		lock.unlock();
	}
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值