远程测试
如果你的 JMeter 客户端机器在性能方面无法模拟足够多的用户来给你的服务器施加压力或在网络级别受到限制,则可以选择从单个 JMeter 客户端控制多个远程 JMeter 引擎。通过远程运行 JMeter,你可以在许多低端计算机上复制测试,从而在服务器上模拟更大的负载。JMeter 客户端的一个实例可以控制任意数量的远程 JMeter 实例,并从中收集所有数据。这提供了以下功能:
- 将测试样本保存到本地机器
- 从单台机器管理多个 JMeterEngine
- 无需将测试计划复制到每个服务器 - 客户端将其发送到所有服务器
注意:所有服务器都运行相同的测试计划。JMeter 不会在服务器之间分配负载,每个服务器都运行完整的测试计划。因此,如果你设置 1000 个线程并拥有 6 个 JMeter 服务器,你最终会注入 6000 个线程。
但是,远程模式确实比独立运行相同数量的 CLI 模式测试使用更多资源。如果使用了许多服务器实例,客户端 JMeter 可能会过载,客户端网络连接也会如此。这已通过切换到剥离模式(见下文)得到改善,但你应始终检查你的客户端是否过载。
请注意,虽然你可以在应用程序服务器上执行 JMeterEngine,但你需要注意这样一个事实,即这将增加应用程序服务器上的处理开销,因此你的测试结果会有些污点。推荐的方法是让一台或多台机器与你配置为运行 JMeter 引擎的应用程序服务器位于同一以太网段上。这将最大限度地减少网络对测试结果的影响,而不会影响应用服务器本身的性能。
步骤 0:配置节点
确保所有节点(客户端和服务器):
- 正在运行完全相同版本的 JMeter。
- 在所有系统上使用相同版本的 Java。使用不同版本的 Java 可能有效,但不鼓励使用。
- 具有 RMI over SSL 的有效密钥库 ,或者你已禁用 SSL。
如果测试使用任何数据文件,请注意这些不是由客户端发送的,因此请确保它们在每个服务器上的相应目录中可用。 如有必要,你可以通过编辑每个服务器上的 user.properties 或 system.properties 文件为属性定义不同的值。这些属性将在服务器启动时被拾取,并可用于测试计划以影响其行为(例如连接到不同的远程服务器)。或者在测试使用的任何数据文件中使用不同的内容(例如,如果每个服务器必须使用唯一的 id,则在数据文件之间划分它们)
步骤 1:启动服务器
要在远程节点上运行 JMeter,请通过运行 JMETER_HOME/bin/jmeter-server
(unix) 或 JMETER_HOME/bin/jmeter-server.bat
(windows) 脚本在你希望运行的所有机器上启动 JMeter 服务器组件。
请注意,除非使用不同的 RMI 端口,否则每个节点上只能有一个 JMeter 服务器。
JMeter 服务器应用程序自己启动 RMI 注册表;无需单独启动 RMI 注册表。
默认情况下,RMI 使用 JMeter 服务器引擎的动态端口。这可能会导致防火墙出现问题,因此你可以定义 JMeter 属性 server.rmi.localport
来控制此端口号。它将用作服务器引擎的本地端口号。
第 2 步:将服务器 IP 添加到客户端的属性文件中
编辑控制 JMeter 机器上的属性文件。在 JMETER_HOME/bin/jmeter.properties
中,找到名为remote_hosts
的属性并添加正在运行的 JMeter 服务器的 IP 地址的值。可以添加多个这样的服务器,以逗号分隔。
请注意,你可以使用-R 命令行选项 来指定要使用的远程主机。这与使用-r 和\-Jremote_hosts={serverlist}
具有相同的效果。例如
jmeter -Rhost1,127.0.0.1,host2
如果你定义 JMeter 属性 server.exitaftertest=true,则服务器将在运行单个测试后退出。另请参见-X 标志(如下所述)
步骤 3a:从 GUI 客户端启动 JMeter 客户端以检查配置
现在你已准备好启动控制 JMeter 客户端。对于 MS-Windows,使用脚本bin/jmeter.bat
启动客户端。对于 UNIX,使用脚本bin/jmeter
。你会注意到 Run 菜单包含两个新的子菜单:Remote Start
和Remote Stop
(见图 1)。这些菜单包含你在属性文件中设置的客户端。使用远程启动和停止来代替正常的 JMeter 启动和停止菜单项。
图 1 - 运行菜单
步骤 3b:从 CLI 模式客户端启动 JMeter
GUI 模式只能用于调试,作为更好的选择,你应该从 CLI 模式(命令行)客户端在远程服务器上启动测试。执行此操作的命令是:
jmeter -n -t script.jmx -r
或者
jmeter -n -t script.jmx -R server1,server2,…
其他可能有用的标志:
-G 属性=值
在所有服务器中定义一个属性(可能出现多次)
-X
在测试结束时退出远程服务器。
第一个示例将在 JMeter 属性 remote_hosts 中定义的任何服务器上开始测试;
第二个示例将从服务器列表中定义 remote_hosts,然后在远程服务器上开始测试。
当所有远程服务器都停止时,命令行客户端将退出。
13.1 设置 SSL
从 JMeter 4.0 开始,RMI 的默认传输机制将使用 SSL。SSL 需要密钥和证书才能工作。你必须自己创建这些密钥。
最简单的设置是为你要连接的所有 JMeter 服务器和客户端使用一个密钥/证书对。JMeter 附带一个脚本来生成一个密钥库,其中包含一个名为 rmi 的密钥(及其相应的证书) 。该脚本位于 bin 目录中,可用于 Windows 系统(称为 bin/create-rmi-keystore.bat
)和类 Unix 系统(称为 bin/create-rmi-keystore.sh
)。它将生成一个密钥对,有效期为 7 天,默认密码短语为changeit
。建议从 bin 目录中调用它。
当你运行该脚本时,它会询问你一些关于它将嵌入证书中的一些名称的问题。只要密钥库工具接受,你就可以输入任何内容。该值必须与默认为 rmi 的属性 server.rmi.ssl.keystore.alias
匹配。创建密钥库的示例会话如下所示。
$ cd jmeter/bin
$ ./create-rmi-keystore.sh
What is your first and last name?
[Unknown]: rmi
What is the name of your organizational unit?
[Unknown]: My unit name
What is the name of your organization?
[Unknown]: My organisation name
What is the name of your City or Locality?
[Unknown]: Your City
What is the name of your State or Province?
[Unknown]: Your State
What is the two-letter country code for this unit?
[Unknown]: XY
Is CN=rmi, OU=My unit name, O=My organisation name, L=Your City, ST=Your State, C=XY correct?
[no]: yes
Copy the generated rmi_keystore.jks to jmeter/bin folder or reference it in property 'server.rmi.ssl.keystore.file'
RMI 的 默认设置 应适用于此设置。将文件 bin/rmi_keystore.jks
复制到要用于分布式测试设置的每个 JMeter 服务器和客户端。
13.2 手动操作
在某些情况下,jmeter-server 脚本可能不适合你(如果你使用的是 JMeter 开发人员未预料到的操作系统平台)。以下是如何使用更手动的过程启动 JMeter 服务器(上面的步骤 1):
步骤 1a:启动 RMI 注册表
从 JMeter 2.3.1 开始,RMI 注册中心是由 JMeter 服务器启动的,所以本节在正常情况下不适用。要恢复到以前的行为,请在服务器主机系统上定义 JMeter 属性 server.rmi.create=false 并按照以下说明进行操作。
JMeter 使用远程方法调用 (RMI) 作为远程通信机制。因此,你需要运行 JDK 自带的 RMI Registry 应用程序(名为rmiregistry
),位于bin
目录下。在运行 rmiregistry 之前,请确保以下 jar 在你的系统类路径中:
- JMETER_HOME/lib/ext/ApacheJMeter_core.jar
- JMETER_HOME/lib/jorphan.jar
- JMETER_HOME/lib/logkit-2.0.jar
rmiregistry 应用程序需要访问某些 JMeter 类。不带参数运行 rmiregistry 。默认情况下,应用程序侦听端口 1099。
步骤 1b:启动 JMeter 服务器
一旦 RMI Registry 应用程序运行,启动 JMeter Server。在 jmeter 启动脚本(jmeter -s
)中使用\-s
选项。
步骤 2 和 3 保持不变。
13.3 提示
JMeter/RMI 需要从客户端到服务器的连接。这将使用你选择的端口,默认为 1099
。
JMeter/RMI 还需要反向连接,以便将示例结果从服务器返回到客户端。
这些将使用高编号端口。
这些端口可以由 jmeter.properties
中名为 client.rmi.localport
的 jmeter 属性控制。
如果它不为零,它将用作客户端引擎的本地端口号的基础。目前 JMeter 将开放最多三个端口,从 client.rmi.localport
中定义的端口开始. 如果 JMeter 客户端和服务器之间有任何防火墙或其他网络过滤器,你需要确保将它们设置为允许连接通过。如有必要,使用监控软件来显示正在生成的流量。
如果你运行的是 Suse Linux,这些提示可能会有所帮助。默认安装可能会启用防火墙。在这种情况下,远程测试将无法正常工作。以下提示由 Sergey Ten 提供。
如果你看到连接被拒绝,请通过以下选项打开调试。
rmiregistry -J-Dsun.rmi.log.debug=true \
-J-Dsun.rmi.server.exceptionTrace=true \
-J-Dsun.rmi.loader.logLevel=verbose \
-J-Dsun.rmi.dgc.logLevel=verbose \
-J-Dsun.rmi.transport.logLevel=verbose \
-J-Dsun.rmi.transport.tcp.logLevel=verbose \
从 JMeter 2.3.1 开始,RMI 注册表由服务器启动;但是这些选项仍然可以从 JMeter 命令行传入。例如:jmeter -s -Dsun.rmi.loader.logLevel=verbose
(即省略-J 前缀)。或者,可以在 system.properties 文件中定义属性。
该问题的解决方案是从/etc/hosts 中删除环回 127.0.0.1 和 127.0.0.2。如果 127.0.0.2 环回不可用, jmeter-server 将无法连接到 rmiregistry 。使用以下设置解决问题。
代替
`dirname $0`/jmeter -s "$@"
和
HOST="-Djava.rmi.server.hostname=[computer_name][computer_domain] \
-Djava.security.policy=`dirname $0`/[policy_file]" \
`dirname $0`/jmeter $HOST -s "$@"
还要创建一个策略文件并将[computer_name][computer_domain]行添加到/etc/hosts。
为了更好地支持远程测试中使用的 RMI 通信通道的 SSH 隧道,自 JMeter 2.6 起:
- 可以设置一个新属性
client.rmi.localport
来控制 RemoteSampleListenerImpl 使用的 RMI 端口 - 为了支持使用本地计算机上的端口通过 SSH 隧道将 RMI 流量作为远程端点进行隧道传输,如果已使用 Java 系统属性
java.rmi.server.hostname
参数直接指定环回接口,则现在允许使用它.
13.4 使用不同的端口
默认情况下,JMeter 使用标准 RMI 端口 1099。有可能改变这一点。要使此功能成功运行,需要同意以下所有内容:
- 在服务器上,使用新的端口号启动 rmiregistry
- 在服务器上,使用定义的属性 server_port 启动 JMeter
- 在客户端上,更新 remote_hosts 属性以包含新的远程主机:端口设置
从 JMeter 2.1.1 开始,jmeter-server 脚本支持更改端口。例如,假设你想使用端口 1664(可能 1099 已被使用)。
在 Windows 上(在 DOS 框中)
C:\JMETER> SET SERVER_PORT=1664
C:\JMETER> JMETER-SERVER [other options]
在 Unix 上:
$ SERVER_PORT=1664 jmeter-server [other options]
[注意环境变量使用大写]
在这两种情况下,脚本都会在指定的端口上启动 rmiregistry,然后在服务器模式下启动 JMeter,并定义了server_port
属性。
所选端口将记录在服务器 jmeter.log 文件中(rmiregistry 不会创建日志文件)。
13.5 使用不同的样本发送者
测试计划中的侦听器将其结果发送回客户端 JMeter,JMeter 将结果写入指定文件 默认情况下,样本在生成时同步发送回来。这会影响服务器测试的最大吞吐量;在线程可以继续之前,必须将样本结果发回。可以设置一些 JMeter 属性来改变这种行为。
模式(mode)
样本发送模式 -自 2.9 起默认为 StrippedBatch 。这应该在客户端节点上设置。
Standard
生成样本后立即同步发送样本
Hold
将样本保存在数组中,直到运行结束。这可能会占用服务器上的大量内存,因此不鼓励这样做。
DiskStore
将样本存储在磁盘文件中(在 java.io.temp 下),直到运行结束。序列化的数据文件在 JVM 退出时被删除。
StrippedDiskStore
从成功的样本中删除 responseData,并使用 DiskStore 发送者发送它们。
Batch
当计数(num_sample_threshold)或时间(time_threshold)超过阈值时发送保存的样本,此时同步发送样本。可以使用以下属性在服务器上配置阈值:
num_sample_threshold
要累积的样本数,默认 100
time_threshold
时间阈值,默认 60000 ms = 60 秒
另见异步模式,如下所述。
Statistical
当计数或时间超过阈值时发送摘要样本。样本按线程组名称和样本标签进行汇总。累积以下字段:
- elapsed time
- latency
- bytes
- sample count
- error count
样本之间不同的其他字段将丢失。
Stripped
从成功的样本中删除 responseData
StrippedBatch
从成功的样本中删除 responseData,并使用 Batch sender 发送它们。
Asynch
样本临时存储在本地队列中。一个单独的工作线程发送样本。这允许测试线程继续进行,而无需等待将结果发送回客户端。但是,如果创建样本的速度快于发送样本的速度,则队列最终会填满,并且采样器线程将阻塞,直到可以从队列中排出一些样本。此模式对于平滑样本生成中的峰值很有用。可以通过在服务器节点上 设置 JMeter 属性 asynch.batch.queue.size(默认 100 )来调整队列大小 。
StrippedAsynch
从成功的样本中删除 responseData,并使用 Async sender 发送它们。
Custom implementation
将 mode 参数设置为你的自定义示例发件人类名称。这必须实现接口 SampleSender 并具有一个构造函数,该构造函数采用 RemoteSampleListener 类型的单个参数。
剥离模式系列剥离 responseData,因此这意味着某些依赖于先前可用 responseData 的元素将无法工作。
这并不是真正的问题,因为总有一种更有效的方法来实现此功能。
以下属性适用于批处理和统计模式:
num_sample_threshold
批次中的样本数(默认 100)
time_threshold
等待的毫秒数(默认 60 秒)
13.6 处理启动失败的节点
对于大规模测试,远程服务器的某些部分可能不可用或关闭。例如,当你使用自动化脚本分配许多云机器并将它们用作生成器时,一些请求的机器可能会因为云的问题而无法启动。从 JMeter 2.13 开始,有新的属性可以控制这种行为。
首先,你可能想要重试初始化尝试,希望失败的节点只是稍微延迟它们的启动。要启用重试,你应该将 client.tries
属性设置为连接尝试的总数。默认情况下,它只进行一次尝试。要控制重试延迟,请将 client.retries_delay
属性设置为尝试之间休眠的毫秒数。
最后,你可能仍希望使用那些成功初始化并跳过失败节点的生成器运行测试。要启用它,请设置 client.continue_on_fail=true
属性。
13.7 使用安全管理器
在分布式环境中运行 JMeter 时,你必须注意,JMeter 基本上是服务器端和客户端的远程执行代理。一旦它破坏了 JMeter 客户端或服务器之一,恶意方可能会使用它来获得进一步的访问权限。为了缓解这种情况,Java 有一个安全管理器的概念,在执行潜在的危险操作之前,JVM 会询问该安全管理器。这些操作可能是解析主机名、创建或读取文件或在操作系统中执行命令。
可以通过设置 Java 系统属性 java.security.manager
和 java.security.policy
来启用安全管理器。请务必查看 控制应用程序的快速浏览 。
使用 setenv.sh(或 Windows 下的 setenv.bat)的新机制,你可以通过将以下代码片段添加到 ${JMETER_HOME}/bin/setenv.sh
来启用安全管理器:
JVM_ARGS=" \
-Djava.security.manager \
-Djava.security.policy=${JMETER_HOME}/bin/java.policy \
-Djmeter.home=${JMETER_HOME} \
"
JVM 现在会将文件 ${JMETER_HOME}/bin/java.policy
中定义的策略添加到可能全局定义的策略中。如果你希望你的定义成为策略的唯一来源,请在设置属性 java.security.policy 时使用两个等号而不是一个等号。
这些策略将取决于你的用例,并且可能需要一段时间才能找到正确的受限制和允许的操作。Java 可以通过属性 java.security.debug 帮助你找到所需的策略。将其设置为访问,它将记录所有被要求允许的权限。只需将以下行添加到你的 setenv.sh
:
JVM_ARGS="${JVM_ARGS} -Djava.security.debug=access"
看起来有点奇怪,我们定义了一个 Java 系统属性 jmeter.home,其值为${JMETER_HOME}。此变量将在示例 java.policy 中用于限制文件系统访问,并仅允许它读取 JMeters 配置和库,并仅限制对特定位置的写入访问。
以下策略定义文件已用于简单的远程测试。当你运行更复杂的场景时,你可能需要调整策略。测试计划位于用户主目录中名为 jmeter-testplans 的目录下。示例 java.policy 如下所示:
grant codeBase "file:${jmeter.home}/bin/*" {
permission java.security.AllPermission;
};
grant codeBase "file:${jmeter.home}/lib/jorphan.jar" {
permission java.security.AllPermission;
};
grant codeBase "file:${jmeter.home}/lib/log4j-api-2.11.1.jar" {
permission java.security.AllPermission;
};
grant codeBase "file:${jmeter.home}/lib/log4j-slf4j-impl-2.11.1.jar" {
permission java.security.AllPermission;
};
grant codeBase "file:${jmeter.home}/lib/slf4j-api-1.7.25.jar" {
permission java.security.AllPermission;
};
grant codeBase "file:${jmeter.home}/lib/log4j-core-2.11.1.jar" {
permission java.security.AllPermission;
};
grant codeBase "file:${jmeter.home}/lib/ext/*" {
permission java.security.AllPermission;
};
grant codeBase "file:${jmeter.home}/lib/httpclient-4.5.6.jar" {
permission java.net.SocketPermission "*", "connect,resolve";
};
grant codeBase "file:${jmeter.home}/lib/darcula.jar" {
permission java.lang.RuntimePermission "modifyThreadGroup";
};
grant codeBase "file:${jmeter.home}/lib/xercesImpl-2.12.0.jar" {
permission java.io.FilePermission "${java.home}/lib/xerces.properties", "read";
};
grant codeBase "file:${jmeter.home}/lib/groovy-all-2.4.15.jar" {
permission groovy.security.GroovyCodeSourcePermission "/groovy/script";
permission java.lang.RuntimePermission "accessClassInPackage.sun.reflect";
permission java.lang.RuntimePermission "getProtectionDomain";
};
grant {
permission java.io.FilePermission "${jmeter.home}/backups", "read,write";
permission java.io.FilePermission "${jmeter.home}/backups/*", "read,write,delete";
permission java.io.FilePermission "${jmeter.home}/bin/upgrade.properties", "read";
permission java.io.FilePermission "${jmeter.home}/lib/ext/-", "read";
permission java.io.FilePermission "${jmeter.home}/lib/ext", "read";
permission java.io.FilePermission "${jmeter.home}/lib/-", "read";
permission java.io.FilePermission "${user.home}/jmeter-testplans/-", "read,write";
permission java.io.SerializablePermission "enableSubclassImplementation";
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.dynalink.support";
permission java.lang.RuntimePermission "accessClassInPackage.sun.awt";
permission java.lang.RuntimePermission "accessClassInPackage.sun.misc";
permission java.lang.RuntimePermission "accessClassInPackage.sun.swing";
permission java.lang.RuntimePermission "accessDeclaredMembers";
permission java.lang.RuntimePermission "createClassLoader";
permission java.lang.RuntimePermission "createSecurityManager";
permission java.lang.RuntimePermission "getClassLoader";
permission java.lang.RuntimePermission "getenv.*";
permission java.lang.RuntimePermission "nashorn.createGlobal";
permission java.util.PropertyPermission "*", "read";
};
使用 java.security.AllPermission
是使你的测试计划有效的一种简单方法,但它可能是通往安全之路的危险捷径。