我们在用 JMeter 做性能测试时可能经常会问一个问题——单个 JMeter 能最大支持多少虚拟用户?这个问题其实很难给出一个很准确的答案。因为虚拟用户本身是一个抽象的概念,每个虚拟用户可以是模拟不同的协议。就像如果别人问某个容器能装多少东西这种问题,因为东西本身不确定的话,谁也无法给出一个确定的答案。当然了,容器大小本身是确定的,只能说在给定的容器的范围内,是否有一些方式来优化,能够让一个容器装下更多的一个确定的东西。毕竟有的时候如果把所有潜能发挥出来,还是很可观的。那 JMeter 有哪些地方可以优化呢?

限制 JMeter 上模拟的虚拟用户的瓶颈主要有计算资源(CPU),存储(内存)和操作系统资源的限制等,下面分开讲述。

计算资源主要指的就是 CPU,不同的测试脚本对 CPU 的使用可能会有很大的差别。在编写、执行测试脚本的时候可以考虑下面的一些问题。

1)JMeter 脚本在运行过程中应该避免循环执行大量计算的工作:比如测试脚本中每个虚拟用户循环使用了 BeanShell 对数据进行处理,如果真的有此需求的话,建议使用扩展 function。或者准备数据的部分是不是只需要执行一次?比如将这部分逻辑放在 “只执行一次” 控制器里。

2)JMeter 在 UI 模式下运行也会消耗更多的 CPU 资源,建议脚本调试通过之后,实际运行测试的时候通过在命令行下来运行测试脚本

3)JMeter 的各种图形化的监听器也会消耗 CPU 资源,在实际的测试运行过程中可以把这些不必要的监听器都关闭,只保留必要的监听器

在自己实现插件的时候,需要考虑实现比较高效的一些算法,如果一个比较差的算法导致耗费额外的 CPU,上千个线程累计起来是非常可观的,所以在插件实现一些偏计算的方面模拟的时候,一定要做到精打细算。

存储主要指的就是内存。JMeter 是由 Java 实现的,而 Java 应用吃内存大家都觉得是很正常,但是这部分是否有优化的空间呢?答案是肯定的。JMeter 和普通的 Java 应用程序一样,启动后使用的内存主要包括两个部分栈和堆。

1)栈空间主要用于分配在方法调用过程中压入栈的方法调用的参数值等。栈空间的使用是和线程数目基本上成正比的,Java 8 中缺省每个线程会分配 1MB 的栈空间。如果使用的是 32 位的系统,由于一个进程的寻址空间为 4GB,假设系统还需要留 1GB 的内存空间,那么就算把所有的内存都分配给栈,最多也就是能创建 3000 个线程。当然,如果是使用了 64 位的系统的话,基本上就没有这个限制了(实际上还受限于操作系统的一些软配置,稍后会提及)。假如你的测试脚本(实际上取决于插件的实现)并没有递归等复杂的栈调用,那么可以把每个线程所需的栈空间调小。调每线程栈空间的使用可以通过打开 jmeter.sh/jmeter.bat,通过加入下面的语句来解决,例子中的配置的意思是每线程使用 400KB 的栈空间,比缺省的 1MB 节省了约 60%,对于需要创建大量的线程的 JMeter 来说,节省的空间还是比较可观的。但是实际上在运行过程中,栈空间的使用也不完全是线性的,JVM 或者操作系统可能在某些地方还是共享了一些栈空间,具体的节省下来的栈空间需要通过试验才能得到准确的数值。

JVM_ARGS="-XX:ThreadStackSize=400k"

2)堆则包括分配对象实例所需要的静态变量、类变量等。这部分所用的内存取决于插件的实现,比如每个 Sampler 所依赖的对象的大小等。这部分空间的调整可以通过设置 Xmx 参数来实现。做法还是通过打开 jmeter.sh/jmeter.bat,下面的例子的意思是上来就在堆空间上分配 15GB 内存,最大可以使用的堆的空间的大小也是 15GB。

JVM_ARGS="-Xms15G -Xmx15G"

在自己实现 JMeter 插件的时候应该仔细考虑以上的问题,比如避免在 Sampler 中再单独启动线程,因为这么做会使每个虚拟用户创建额外的一个线程,从而可能导致在同样的配置下,你实现的插件创建少一半的虚拟用户。比较好的做法是所有虚拟用户通过一个线程来处理,不过这样也会导致多线程之间数据使用的冲突等问题,需要根据自己的情况酌情处理。针对堆空间的使用,如果有比较占存储空间的类变量,可能尽量多线程共享一份数据(比如通过静态变量等),而不是每线程创建自己的实例,当然还是需要考虑多线程访问的时候变量保护的问题。

操作系统的缺省配置可以满足大部分用户的日常使用,而性能测试往往会突破这些操作系统默认的配置。常见的包括文件、端口限制等。我们以 CentOS 为例,看看如何优化这些配置。

1)设定每个进程可以打开的最大文件描述符的数量,由于在 Linux 中一个 socket 连接也是文件描述符,而性能测试过程过程中往往测试的时候也需要生成一个 socket 连接,因此该参数的设置会影响到最大模拟的虚拟用户数。

ulimit -n 65535

2)设置系统可用的 socket 端口号,每台机器最多可用的端口号为 65535,在测试机器上可能某些系统的端口已经被占用,因此用户可以设置可用的端口号段来增加可用的端口。如下例所示可用的端口号为 15000 至 61000,那么最多的可用端口号数目为 46000 个。如果需要设置 Docker 容器中的该配置,需要在特权模式下才能对其进行配置,否则该项配置是只读的 (docker run --privileged)

sysctl -w net.ipv4.ip_local_port_range="15000 61000" 

3)tcp_tw_reuse 表示可以复用处于 TIME_WAIT 状态的连接,对于在性能测试过程中可能产生的大量临时的短连接,该选项可以重用连接,而不用等待连接的完全释放,从而能提高支持的并发用户数目。tcp_tw_recycle 用于回收处于 TIME_WAIT 状态的连接,也可以提高连接的使用率。

sysctl -w net.ipv4.tcp_tw_reuse=1 

sysctl -w net.ipv4.tcp_tw_recycle=1

4)提高线程的使用限制。pid_max 用于控制操作系统线程 ID 的最大值,该值会影响可以创建的最大的线程数目。max_map_count 单进程 mmap 的限制会影响当个进程可创建的线程数,需要将该值也提高以支持创建更多的线程。

echo 999999 > /proc/sys/kernel/pid_max

echo 1999999 > /proc/sys/vm/max_map_count


↙↙↙阅读原文可查看相关链接,并与作者交流