Contents

JMeter、Gatling 和 The Grinder 测试工具比较

1. 简介

为工作选择正确的工具可能会令人生畏。在本教程中,我们将通过比较三个 Web 应用程序负载测试工具(Apache JMeter、Gatling 和 The Grinder)与一个简单的 REST API 来简化这一点。

2.负载测试工具

首先,让我们快速回顾一下每个方面的一些背景。

2.1. Gatling

Gatling 是一个负载测试工具,可以在 Scala 中创建测试脚本。Gatling 的记录器生成 Scala 测试脚本,这是 Gatling 的一个关键特性。 查看我们的 Gatling 简介  教程以获取更多信息。

2.2. JMeter

JMeter 是 Apache 的负载测试工具。它提供了一个不错的 GUI,我们可以使用它来进行配置。称为逻辑控制器的独特功能为在 GUI 中设置测试提供了极大的灵活性。

请访问我们的 JMeter 简介  教程以获取屏幕截图和更多说明。

2.3. Grinder

我们的最后一个工具The Grinder 提供了比其他两个更基于编程的脚本引擎,并使用了 Jython。但是,The Grinder 3 确实具有录制脚本的功能。

Grinder 还与其他两个工具不同,它允许控制台和代理进程。此功能为代理进程提供了能力,以便负载测试可以跨多个服务器扩展。  它专门宣传为为开发人员构建的负载测试工具,用于发现死锁和减速。

3. 测试用例设置

接下来,对于我们的测试,我们需要一个 API。我们的 API 功能包括:

  • 添加/更新奖励记录
  • 查看一个/所有奖励记录
  • 将交易链接到客户奖励记录
  • 查看客户奖励记录的交易

我们的场景:

一家商店正在全国范围内进行销售,新客户和回头客需要客户奖励账户来节省开支。奖励 API 通过客户 ID 检查客户奖励帐户。如果不存在奖励帐户,请添加它,然后链接到交易。

之后,我们查询交易。

3.1. 我们的 REST API

让我们通过查看一些方法存根来快速了解 API:

@PostMapping(path="/rewards/add")
public @ResponseBody RewardsAccount addRewardsAcount(@RequestBody RewardsAccount body)
@GetMapping(path="/rewards/find/{customerId}")
public @ResponseBody Optional<RewardsAccount> findCustomer(@PathVariable Integer customerId)
@PostMapping(path="/transactions/add")
public @ResponseBody Transaction addTransaction(@RequestBody Transaction transaction)
@GetMapping(path="/transactions/findAll/{rewardId}")
public @ResponseBody Iterable<Transaction> findTransactions(@PathVariable Integer rewardId)

请注意一些关系,例如通过奖励 id 查询交易和通过客户 id 获取奖励帐户。这些关系为我们的测试场景创建强制执行一些逻辑和一些响应解析。

被测应用程序还使用 H2 内存数据库进行持久化。

幸运的是,我们的工具都能很好地处理它,有些比其他的要好。

3.2. 我们的测试计划

接下来,我们需要测试脚本。

为了公平比较,我们将为每个工具执行相同的自动化步骤:

  1. 生成随机客户帐户 ID
  2. 发布交易
  3. 解析随机客户 ID 和交易 ID 的响应
  4. 使用客户 ID 查询客户奖励帐户 ID
  5. 解析奖励帐户 ID 的响应
  6. 如果不存在奖励帐户 ID,则添加一个带有帖子的帐户
  7. 使用交易 ID 发布相同的初始交易并更新奖励 ID
  8. 通过奖励账户id查询所有交易

3.3. Gatling

对于 Gatling 而言,熟悉 Scala 为开发人员带来了福音,因为 Gatling API 很健壮并且包含很多特性。

Gatling 的 API 采用了构建器 DSL 方法,正如我们在其第 4 步中所见:

.exec(http("get_reward")
  .get("/rewards/find/${custId}")
  .check(jsonPath("$.id").saveAs("rwdId")))

特别值得注意的是,当我们需要读取和验证 HTTP 响应时,Gatling 对 JSON 路径的支持。在这里,我们将获取奖励 id 并将其保存到 Gatling 的内部状态。

此外,Gatling 的表达式语言使动态请求正文字符串更容易:

.body(StringBody(
  """{ 
    "customerRewardsId":"${rwdId}",
    "customerId":"${custId}",
    "transactionDate":"${txtDate}" 
  }""")).asJson)

最后是我们用于此比较的配置。1000 次运行设置为整个场景的重复,atOnceUsers方法设置线程/用户:

val scn = scenario("RewardsScenario")
  .repeat(1000) {
  ...
  }
  setUp(
    scn.inject(atOnceUsers(100))
  ).protocols(httpProtocol)

3.4. JMeter

**JMeter 在 GUI 配置后生成一个 XML 文件。**该文件包含具有设置属性及其值的 JMeter 特定对象,例如:

<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Transaction" enabled="true">
<JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Transaction Id Extractor" enabled="true">

查看testname属性,当我们识别出它们与上述逻辑步骤匹配时,它们可以被标记。添加子项、变量和依赖项步骤的能力为 JMeter 提供了脚本所提供的灵活性。此外,我们甚至为我们的变量设置了范围!

我们在 JMeter 中运行和用户的配置使用ThreadGroups

<stringProp name="ThreadGroup.num_threads">100</stringProp>

3.5. Grinder

如果没有 Scala 和 GUI 的函数式编程,我们的 The Grinder 的 Jython 脚本看起来非常基础。添加一些系统 Java 类,我们的代码行数就会少很多。

customerId = str(random.nextInt());
result = request1.POST("http://localhost:8080/transactions/add",
  "{"'"customerRewardsId"'":null,"'"customerId"'":"+ customerId + ","'"transactionDate"'":null}")
txnId = parseJsonString(result.getText(), "id")

但是,更少的测试设置代码行需要更多的字符串维护代码(例如解析 JSON 字符串)来平衡。此外,HTTPRequest API 的功能也很精简。

使用 The Grinder,我们在外部属性文件中定义线程、进程和运行值:

grinder.threads = 100
grinder.processes = 1
grinder.runs = 1000

4. 试运行

4.1. 测试执行

这三个工具都建议使用命令行进行大型负载测试。 为了运行测试,我们将使用 Gatling开源 版本 3.4.0 作为独立工具、JMeter 5.3 和 The Grinder版本 3

Gatling 只需要我们设置 JAVA_HOMEGATLING_HOME。要执行 Gatling,我们使用:

./gatling.sh

在 GATLING_HOME/bin 目录中。 JMeter在启动GUI进行配置时需要一个参数来禁用测试的GUI:

./jmeter.sh -n -t TestPlan.jmx -l log.jtl

与 Gatling 一样,The Grinder 要求我们设置JAVA_HOMEGRINDERPATH。但是,它还需要更多属性:

export CLASSPATH=/home/lore/Documents/grinder-3/lib/grinder.jar:$CLASSPATH
export GRINDERPROPERTIES=/home/lore/Documents/grinder-3/examples/grinder.properties

如上所述,我们提供了一个grinder.properties文件用于附加配置,例如线程、运行、进程和控制台主机。

最后,我们使用以下命令引导控制台和代理:

java -classpath $CLASSPATH net.grinder.Console
java -classpath $CLASSPATH net.grinder.Grinder $GRINDERPROPERTIES

4.2. 试验结果

每个测试使用 100 个用户/线程运行 1000 次。让我们解开一些亮点:

成功的请求 错误 总测试时间(秒) 平均响应时间(毫秒) 平均吞吐量
加特林 500000 个请求 0 218s 42 2283 个请求/秒
JMeter 499997 个请求 0 237s 46 2101 个请求/秒
磨床 499997 个请求 0 221s 43 2280 个请求/秒

结果显示这 3 个工具具有相似的速度,基于平均吞吐量,Gatling 略微超过其他 2 个。

每个工具还在更友好的用户界面中提供附加信息。

**Gatling 将在运行结束时生成一个 HTML 报告,其中包含多个图表和统计信息,用于总运行以及每个请求。**以下是测试结果报告的片段:

/uploads/gatling_jmeter_grinder_comparison/1.png

使用 JMeter 时,我们可以在测试运行后打开 GUI,并根据我们保存结果的日志文件生成 HTML 报告:

/uploads/gatling_jmeter_grinder_comparison/3.png JMeter HTML 报告还包含每个请求的统计信息细分。 最后,Grinder 控制台记录每个代理的统计信息并运行:

/uploads/gatling_jmeter_grinder_comparison/5.png 虽然 The Grinder 是高速的,但它的代价是额外的开发时间和更少的输出数据多样性。