13. 远程测试
如果您的 JMeter 客户端机器在性能方面无法模拟足够多的用户来给您的服务器施加压力或在网络级别受到限制,则可以选择从单个 JMeter 客户端控制多个远程 JMeter 引擎。通过远程运行 JMeter,您可以在许多低端计算机上复制测试,从而在服务器上模拟更大的负载。JMeter 客户端的一个实例可以控制任意数量的远程 JMeter 实例,并从中收集所有数据。这提供了以下功能:
- 将测试样本保存到本地机器
- 从单台机器管理多个 JMeterEngine
- 无需将测试计划复制到每个服务器 - 客户端将其发送到所有服务器
但是,远程模式确实比独立运行相同数量的 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 启动和停止菜单项。
步骤 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 服务器和客户端使用一个密钥/证书对。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
步骤 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 属性来改变这种行为。
- 模式
- 样本发送模式 -自 2.9 起默认为StrippedBatch 。这应该在客户端节点上设置。
- 标准
- 生成样本后立即同步发送样本
- 抓住
- 将样本保存在数组中,直到运行结束。这可能会占用服务器上的大量内存,因此不鼓励这样做。
- 磁盘存储
- 将样本存储在磁盘文件中(在java.io.temp下),直到运行结束。序列化的数据文件在 JVM 退出时被删除。
- 剥离磁盘存储
- 从成功的样本中删除 responseData,并使用 DiskStore 发送者发送它们。
- 批
- 当计数(num_sample_threshold)或时间(time_threshold)超过阈值时发送保存的样本,此时同步发送样本。可以使用以下属性在服务器上配置阈值:
- num_sample_threshold
- 要累积的样本数,默认100
- 时间阈值
- 时间阈值,默认 60000 ms = 60 秒
- 统计
- 当计数或时间超过阈值时发送摘要样本。样本按线程组名称和样本标签进行汇总。累积以下字段:
- 经过时间
- 潜伏
- 字节
- 样本数
- 错误计数
- 剥离
- 从成功的样本中删除 responseData
- 剥离批次
- 从成功的样本中删除 responseData,并使用 Batch sender 发送它们。
- 异步
- 样本临时存储在本地队列中。一个单独的工作线程发送样本。这允许测试线程继续进行,而无需等待将结果发送回客户端。但是,如果创建样本的速度快于发送样本的速度,则队列最终会填满,并且采样器线程将阻塞,直到可以从队列中排出一些样本。此模式对于平滑样本生成中的峰值很有用。可以通过在服务器节点上 设置 JMeter 属性asynch.batch.queue.size(默认100 )来调整队列大小 。
- 剥离异步
- 从成功的样本中删除 responseData,并使用 Async sender 发送它们。
- 自定义实现
- 将 mode 参数设置为您的自定义示例发件人类名称。这必须实现接口SampleSender并具有一个构造函数,该构造函数采用RemoteSampleListener类型的单个参数。
这并不是真正的问题,因为总有一种更有效的方法来实现此功能。
以下属性适用于批处理和统计模式:
- num_sample_threshold
- 批次中的样本数(默认100)
- 时间阈值
- 等待的毫秒数(默认 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"; };