如何将bug杀死在摇篮里?解读压测必经之路JMeter分布式的技术点

JMeter是当前Web性能测试中应用最为广泛的工具,简洁强大的界面,开源免费的授权,以及广泛的插件扩展,使得JMeter能满足几乎所有Web场景的性能测试。然而,单机性能的限制,是JMeter一直以来最大的诟病。

由于采用Java多线程进行并发用户的模拟,使得线程数的增加自然增加了测试机的资源消耗。一边是被测系统并发数的日益提高,一边是JMeter单机性能的掣肘。测试人员仿佛是走钢丝的杂技演员,平衡木的一边是并发数,一边是测试机的资源,战战兢兢、小心谨慎的想找到其中的平衡。

打开凤凰新闻,查看更多高清图片

实际上JMeter提供了一种分布式压测的方法提高并发能力。本文介绍如何配置一个JMeter的分布式测试环境,并对单机和分布式的测试机资源占用情况进行对比。最后,对分布式的线程数、并发机制以及影响分布式TPS的采样信息回送模式进行说明。

一、引入JMeter分布式

JMeter是Web性能测试中的利器,基本属于Web压测的事实标准。然而使用过JMeter的测试人员会发现,当并发用户增加后,JMeter本身的性能也会急剧下降,导致无法对被测系统施加压力。

以一个实际环境为例,系统配置为:Windows 7旗舰版,i5-4590单CPU4核4线程,4G内存。使用JMeter3.1,Java8进行测试。

当并发线程设置为1000时,资源占用如下图。

继续增加线程个数为1500,此时运行出现错误如下。

查看日志中的记录,可以看到由于内存不足,无法启动新的线程。

如果我们需要更多的线程并发,此时就必须使用JMeter的分布式压测了。

二、环境搭建

遵照JMeter官网的方法,很容易搭建适合自己需要的分布式环境。以下图的实际环境为例,测试机A作为压测的协调主机(即上述的机器),B和C作为执行机,实际完成压力的发送,被测系统位于D上。

具体配置过程如下:

1、执行机属性配置。配置执行机RMI服务的端口,修改\apache-jmeter-3.1\bin下的jmeter.properties,将如下属性修改为指定的端口:server_port和server.rmi.localport。本例中B均修改为1039,C均修改为1058。如下图:

2、启动执行机的监听服务。在执行机上启动服务,打开\apache-jmeter-3.1\bin下的jmeter-server.bat即可。启动图如下。

说明在B的1039端口,C的1058端口均启动了执行的服务,等待协调主机的命令。

3、协调主机属性配置。配置协调主机连接的执行机和端口,修改\apache-jmeter-3.1\bin下的jmeter.properties,将remote_hosts属性修改为需要连接的执行机IP和端口。如下图。

4、执行测试。启动协调主机的JMeter,通过界面进行启动。

如上图,通过Run->Remote Start可以启动单个执行机,通过Remote Start All启动全部执行机。

三、资源对比

在前面我们看到,单台如上配置的测试机在启动1500线程时会出现内存不够用的情况。那么分布式的情况如何呢?

下图为协调主机的资源占用情况。

其中一台执行机的资源占用情况,执行机B,Windows10 教育版,i5-6500单CPU4核心4线程(3.20GHz),8G内存。

另外一台执行机的资源占用情况,执行机C,配置为Windows Server 2012 R2 Standard,Intel Xeon单CPU6核12线程。

四、注意事项

通过JMeter分布式进行压测,可以避免单机测试机资源的限制,提高压测的并发线程数。然而,还有一些细节需要注意。

1)线程数

JMeter分布式测试,是通过网络连接将协调主机载入的脚本分别传递(复制)给执行机,也就是说每个执行机拿到的脚本都是一致的,所以在每台执行机都会启动脚本中线程组指定的并发线程数。这样在设定脚本线程数目时,需要除以执行机个数,设定并发线程数。

以上述示例进行说明,期望完成1500用户并发,在单机测试时脚本设置并发线程为1500,而在有两条执行机的情况下需要设定为750,脚本修改如下。

此时启动远程测试Remote Start All后,看到界面的显示如下。

其中的1500/0,含义是一共启动了1500个线程,本机为0。

2)同步定时器的使用

我们知道,同步定时器(Synchronizing Timer)的意义在于提供瞬间压力的测试,其原理是阻塞期望个数的线程(用户),在同时进行释放,尽可能真实的模拟并发用户同时进行某操作的情况。那么在分布式并发的情况是如何的呢?我们将上述例子简化为20用户并发,即每个执行机10个线程,同时增加一个同步定时器,期望15个线程瞬间压测。脚本修改如下。

执行远程测试Remote Start All,监控结果树的情况。会发现没有任何请求被发送。这是因为,同步定时器仅在一个JVM中起作用,而分布式环境下属于两台机器的两个独立JVM,同步定时器无法生效。此时对于每个执行机,均启动了10个线程,但获得的脚本中需要等待20个线程再释放,所以两个执行机均不会向下执行。

JMeter官网原文如上,含义就是同步定时器阻塞线程的机制仅在一个JVM中,所以在分布式的情况下,设定的阻塞线程数不能超过每个执行机的并发线程数。本例中就是不能超过10线程。

类似的,高斯随机定时器(Gaussian Random Timer),固定定时器(Constant Timer),均匀随机定时器(Uniform Random Timer),泊松随机定时器(Poisson Random Timer),BeanShell定时器,BSF定时器,JSR定时器。由于是针对单线程的,所以不受分布式的影响。

当然,吞吐量定时器也是对于每个执行机独立进行限制的。也就是说,设置的吞吐量限定值,由于脚本是分别在每个执行机进行运行的,所以限定的也都是当前作用的执行机。

3)执行机测试结果回送方式

JMeter分布式的原理十分容易理解,在实际测试中就如同蝴蝶效应一样,一点细微的差别都会导致最终加压结果的巨大差异,从而导致最终测试结果的不确定性。在分布式测试中,执行机测试结果的回送方式影响非常大。

首先我们对比一下实际的测试结果。在协调主机和执行机上的配置文件\apache-jmeter-3.1\bin下的jmeter.propertie中,修改mode=StrippedBatch,如下截图。

注意在协调主机和执行机上均要修改。运行测试,此时我们查看协调主机的网络使用率和测试的TPS,可以发现对于百兆网卡使用了其中7%的流量,对于被测系统的TPS为461。

此时查看其中一个执行机的网络使用情况,也在可以接收的范围内。

接下来,修改mode=Standard,进行同样的测试。观察协调主机的测试结果如下。

可以看到100M网卡已经被消耗殆尽,使用率达到97.3%,而TPS也只有92,可以想象,此时对被测系统压力是非常有限的。同样的,可以看到执行机的网络使用情况如下。

对比之前的结果,也是让人堪忧的。可以想象,以这样的测试环境对被测系统进行测试,得到的测试结果能有多大的可信度。

JMeter官网有非常具体的描述,主要内容如下。脚本中的监听器会根据配置将测试结果回传,默认情况下当结果产生后同步回传到协调主机。这会影响测试的最大吞吐量,JMeter提供了配置属性对此进行干预,即jmeter.propertie文件中的mode属性。该属性可以有如下取值:

1)Standard:测试中的采样信息产生的同时回传给协调主机。

2)Hold:在测试结束前将采样信息保存在数组中。这种模式会占用执行机大量内存,不推荐使用。

3)DiskStore:在测试结束前,将结果存储在硬盘文件(由java.io.temp属性指定)中。这种序列化文件会在JVM退出后删除。

4)StrippedDiskStore:将成功的响应数据在采样信息中删除,之后使用DiskStore的方式处理。

5)Batch:当采样信息超过指定的阈值后,同步将采样信息发送。阈值可以是采样个数阈值(num_sample_threshold)或者时间(time_threshold),这两个阈值可以在执行机的jmeter.propertie文件中指定。

num_sample_threshold:累加的采样个数,默认为100。

time_threshold:时间阈值,默认为60000ms(即60秒)。

后面的Asynch模式与该模式类似。

6)Statistical:当采样信息超过指定阈值后,发送采样信息的摘要信息。根据线程组和采样名称进行表记。摘要包括如下字段:

Elapsed time:流逝时间

Latency:延迟

Bytes:字节

Sample count:采样个数

Error count:错误个数

其他字段会被丢弃。(5)中的两个属性也对该模式起作用。

7)Stripped:将成功的采样在响应数据中删除。

8)StrippedBatch:在响应数据中删除成功采样后,使用Batch模式。

9)Asynch:采样信息在本地队列中临时存储。启动一个独立的工作线程进行采样信息发送。这样能够允许测试线程得以持续执行而不用等待回送的结果返回。当然,当结果产出太快,导致临时队列被充满,也会使得测试线程阻塞,直到队列中一些数据排空。该模式可以用来削平采样信息的网络波峰。通过执行机JMeter的属性asynch.batch.queue.size?(默认100)进行队列大小配置。

10)StrippedAsynch:在响应数据中删除成功采样,再使用Asynch模式。

11)自定义:通过配置一个特定的Java类,可以配置自行扩展的发送模式。扩展类必须首先接口SampleSender,并且实现接收一个具有RemoteSampleListener类型变量的构造函数。

上述提到的Stripped类的模式,都会将响应数据中的成功信息进行删除,所以一些依赖前置响应数据的元件会出现问题。这个问题可以通过调整测试脚本得以解决。