使用JMeter做MongoDB性能测试

我们先了解一点MongoDB的知识,然后,学习构建一个用于测试的脚本。

对大多数应用环境来说,数据库是一个关键要素。如何存储数据以及在哪里存储数据,对整个系统的性能会产生巨大影响。因此,在做开发之前,数据库的选择肯定是最重要的决定之一。对数据库进行性能测试有助于你达成此项决定,这也是你在开发过程中的一项重要工作。

这篇文章会教你使用Apache JMeter™进行开源MongoDB数据库测试。我们看看到如何来做:

  • 连接MongoDB
  • 在MongoDB中写入文档(译者注:此处文档指表中的记录行)
  • 从MongoDB中读取文档
  • 在MongoDB中更新文档
  • 从MongoDB中删除文档

使用JMeter进行性能测试

如果你对应用程序出现性能问题,既可能是低效的数据库查询问题,也可能是不充足的数据库服务器。如果你有一个关系型数据库,JMeter的JDBC请求案例允许你执行一个SQL查询并评估其性能。但有时候,一个非关系数据库对于你的需求来说是一个更有效的选择,因此你需要使用JMeter加载测试以找到一个不同的方法。

MongoDB是一种非常流行的非关系型数据库,它使用“文档”这种结构存储数据。MongoDB的实例发送给一个查询。不过,这一操作在查询执行期间会实现对数据库的锁定。这会限制你一次只能发起一个请求,这对性能测试来说是不够的。

幸运的是,通过使用JSR223样例和MongoDBJava驱动库,你可以在Java中写请求测试你的MongoDB样例。我们来了解一点关于MongoDB的知识,然后学习构建一个用于测试的脚本。

MongoDB是什么?

MongoDB是一个免费的,开源的,跨平台的,非关系型,基于文档的数据库,其数据存储于JSON类文档:

{
firstName: "Tester",
lastName: "Testovsky",
age: 30,
occupation: "QA engineer",
skills: [
"JMeter",
"Load Testing",
"Bad Puns"
]
address: [{
city: "Quality-city",
street: "Performance ave.",
house: 12
}]
}

一个文档是一组字段值对,此处的值可以是任何BSON数据类型,数组,其他文档和文档数组。

在MongoDB中,文档存储在所谓的“集合”(类似于关系型数据库的表)当中。集合存储在数据库中,每个MongoDB服务器包含大量数据库。

MongoDB Java 驱动

通过java代码使用有力的MongoDBJava 驱动控制你的MongoDb实例是可以实现的。这个库为你提供了连接MongoDB实例的能力;用它可以创建,读取,更新和删除文档乃至做更多工作。这里可以找到完整的3.0版的API文档。还有特别有用的带有实例和教程参考指南。

为了在JMeter脚本中使用MongoDBJava 驱动,下载最近的mongo-java-driver jar 文件,并将其放在你的JMeter主文件夹的ib/ext文件夹下面。

注意:迄今为止,JMeter发布版有一个旧版本的存放在mongo-java驱动库。这会导致大量的兼容性问题,因此,为了免出问题,从lib文件夹下删除旧有的jar格式 mongo-java驱动文件。

我们来看一下,我们如何在一个JSR233案例使用这个驱动完成基本操作来评估我们的数据库的性能。

JMeter连接MongoDB数据库

为了测试你的数据库性能,你需要首先通过你的JMeter脚本连接数据库。这可以通过JMeter JSR223案例实现。你可以使用这个例子评估一个连接过程的性能,然后使用这种建立的连接检查查询DB入口的性能。依赖你的数据库系统配置,可能需要在连接过程中完成指定的行为。我们来看一些基本案例。

使用指定的端口27017连接localhost上的MongoDB客户端:

import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
MongoClient mongoClient = MongoClients.create();

你可以指定一个连接串作为MongoClients.create() 方法的参数:

MongoClient mongoClient=MongoClients.create("mongodb://mongohost:27017");

如果你需要提供连接到MongoDB客户端的证明:

MongoClient mongoClient=MongoClients.create("mongodb://user:password@mongohost/?authSource=userdb&ssl=true");

你可能经常会使用JMeter变量作为一个MongoClients.create()方法的参数。为了保证你的脚本的可读性,你可以使用一个MongoClientSettings类。例如:

import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import java.util.Arrays;
String mongoUser =vars.get("mongoUser");
String userDB =vars.get("userDB");
char[] password =vars.get("password").toCharArray();
MongoCredential credential =MongoCredential.createCredential(mongoUser, userDB, password);
MongoClientSettings settings =MongoClientSettings.builder()
.applyToClusterSettings {builder ->
builder.hosts(Arrays.asList(newServerAddress(vars.get("mongoHost"),vars.get("mongoPort").toInteger())))}
.build();
MongoClient mongoClient =MongoClients.create(settings);

在你和客户端建立连接之后,可以访问数据库和集合:

import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
MongoDatabase database =mongoClient.getDatabase("jmeter_test");
MongoCollection collection =database.getCollection("blazemeter_tutorial");

1. 这是在JMeter变量“mongoHost,” “databaseName,” 和“collectionName.”中定义的连接一个数据库的完整代码。我们会在随后的JMeter脚本中使用。

import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoClient;
import com.mongodb.MongoClientSettings;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import java.util.Arrays;
try {
MongoClientSettings settings =MongoClientSettings.builder()
.applyToClusterSettings {builder ->
builder.hosts(Arrays.asList(newServerAddress(vars.get("mongoHost"),vars.get("mongoPort").toInteger())))}
.build();
MongoClient mongoClient =MongoClients.create(settings);
MongoDatabase database =mongoClient.getDatabase(vars.get("databaseName"));
MongoCollection collection =database.getCollection(vars.get("collectionName"));
vars.putObject("collection",collection);
return "Connected to " +vars.get("collectionName");
}
catch (Exception e) {
SampleResult.setSuccessful(false);
SampleResult.setResponseCode("500");
SampleResult.setResponseMessage("Exception:" + e);
}

现在,当你有了一个MongoCollection对象,你可以最终使用文档开始工作,例如将数据存储在数据库中。

如何创建一个文档并使用JMeter

将其插入到MongoDB数据库中

如果你的应用程序创建新的文档并将其插入数据库,然后检查的将一个新文档插入数据库中的过程的性能很重要。根据以前的例子我们可以使用JSR223案例。

首先,我们导入必要的库:

import com.mongodb.client.MongoCollection;
import org.bson.Document;
import java.util.Arrays;

现在,我们创建一个文档对象,并设置其字段和值:

Document document = newDocument("firstName", "Expert")
.append("lastName","Protocolson")
.append("age", 37)
.append("occupation","DevOps")
.append("skills",Arrays.asList("System Administration", "Linux"))
.append("address", new Document("city","Systemberg")
.append("street", "DataLine")
.append("house", 42));

接下来,我们把创建的文档插入我们的集合:

collection.insertOne(document);

每个MongoDB文档会拥有一个具有唯一值的”_id”字段。如果文档创建时没有这样的字段或值,Java驱动会自动将一个具有唯一值的”_id”字段插入集合。不需要手动提供”_id”字段。

创建一个文档列表并将其插入集合:

import com.mongodb.client.MongoCollection;
import org.bson.Document;
import java.util.List;
import java.util.ArrayList;
List documents = new ArrayList();
documents.add(document1);
documents.add(document2);
collection.insertMany(documents);

下面是创建一个新文档和插入一个集合过程的完整代码,我们会在我们后面的JMeter脚本中使用。

import com.mongodb.client.MongoCollection;
import org.bson.Document;
import java.util.Arrays;
try {
MongoCollection collection =vars.getObject("collection");
Document document = newDocument("firstName", "Expert")
.append("lastName","Protocolson")
.append("age", 37)
.append("occupation","DevOps")
.append("skills",Arrays.asList("System Administration", "Linux"))
.append("adress", newDocument("city", "Systemberg")
.append("street", "DataLine")
.append("house", 42));
collection.insertOne(document);
return "Document inserted";
}
catch (Exception e) {
SampleResult.setSuccessful(false);
SampleResult.setResponseCode("500");
SampleResult.setResponseMessage("Exception:" + e);
}

为了从集合中获取文档,你要使用MongoCollection对象的find()方法,我们会把代码放到JSR223样例中。例如,以下代码:

import com.mongodb.client.MongoCollection;
import org.bson.Document;
import java.util.List;
List result = collection.find();

会发现集合中的所有文档,并将其写入到结果列表中。

你可以把一个文档对象作为过滤器传递给find()方法:

List result = collection.find(newDocument("age", new Document("$gte", 18)
.append("$lt", 66))
.append("occupation","Developer"));

我们可以在这里找到所有年龄大于等于18岁,小于66岁的程序员。使用Filters的helper方法可以获取同样的列表:

import staticcom.mongodb.client.model.Filters.*;
List result =collection.find(and(gte("age", 2), lt("age", 5),eq("occupation", "Developer")));

以下是在我们的集合中找到一个文档的完整代码。我们会在后面的JMeter脚本中使用。

import com.mongodb.client.MongoCollection;
import staticcom.mongodb.client.model.Filters.*;
import org.bson.Document;
import org.bson.types.ObjectId;
try {
MongoCollection collection =vars.getObject("collection");
Document result =collection.find(eq("firstName", "Expert")).first();
vars.put("exampleDocumentId",result.get("_id").toString());
return "Document with id=" +result.get("_id") + " found";
}
catch (Exception e) {
SampleResult.setSuccessful(false);
SampleResult.setResponseCode("500");
SampleResult.setResponseMessage("Exception:" + e);

你可以在这里找到一个有用的过滤器列表。

在数据库中使用一个文档

要更新集合中的文档,你可以使用MongoCollection对象的updateOne()方法。同样的方法可以如前文所述,用于查询更新文档。例如,JSR223样例中的代码:

import com.mongodb.client.MongoCollection;
import staticcom.mongodb.client.model.Filters.*;
import staticcom.mongodb.client.model.Updates.*;
import org.bson.types.ObjectId;
collection.updateOne(
eq("_id", new ObjectId("5bb43f18ce8cdca890b72422")),
combine(set("occupation","Project Manager"), set("address.city", "NewCodeshire"), currentDate("lastModified")));

在集合中,让”_id”字段值等于”57506d62f57802807471dd41″,给文档改变 “occupation”和”address.city” 字段值,并设置”lastModified”字段为当前日期。

如果你需要编辑几个文档,可以使用updateMany()方法:

collection.updateOne(
and(eq("address.city","Saint Java"), eq("address.street", "Bugsstreet")),
combine(set("address.street","Features blvd."), currentDate("lastModified")));

以上代码改变了所有居住在Saint Java城的居民的街道名称,将值Bugs street改为Features blvd。

下面是更新我们文档值的完整代码。我们会在后面的JMeter脚本中使用。

import com.mongodb.client.MongoCollection;
import staticcom.mongodb.client.model.Filters.*;
import staticcom.mongodb.client.model.Updates.*;
import org.bson.Document;
import org.bson.types.ObjectId;
try {
MongoCollection collection =vars.getObject("collection");
collection.updateOne(
eq("_id", newObjectId(vars.get("exampleDocumentId"))),
combine(set("occupation","Project Manager"), set("adress.city", "NewCodeshire"), currentDate("lastModified")));
return "Document with id=" +vars.get("exampleDocumentId") + " modified";
}
catch (Exception e) {
SampleResult.setSuccessful(false);
SampleResult.setResponseCode("500");
SampleResult.setResponseMessage("Exception:" + e);

所有更新操作的列表可以在这里找到。

从数据库中删除文档

删除文档非常类似于找到文档。使用MongoCollection对象的deleteOne()方法来删除匹配指定过滤器的第一个文档,或者使用deleteMany()删除所有匹配文档。我们会使用JSR223样例。

下面谈谈如何从集合中删除一个文档(是的,我们会在后面的JMeter脚本中使用它):

import com.mongodb.client.MongoCollection;
import static com.mongodb.client.model.Filters.*;
import org.bson.Document;
try {
MongoCollection collection =vars.getObject("collection");
collection.deleteOne(eq("occupation","Project Manager"));
return "Document deleted";
}
catch (Exception e) {
SampleResult.setSuccessful(false);
SampleResult.setResponseCode("500");
SampleResult.setResponseMessage("Exception:" + e);
}

创建你的JMeter测试计划

现在,让我们尝试写一个简单的JMeter脚本来评估我们的MongoDB配置的性能。在我们的脚本中会给每一个连接数据库的操作创建一个JSR223样例:包括插入,读取,更新,和删除文档操作。

先决条件:你已经安装了一个MongoDB客户端,并运行在你的本地主机的默认端口27017上,建立了一个连接。有一个带有空集合”blazemeter_tutorial”的空数据库。

1.在测试计划或者用户定义的变量中,指定必要的变量:

mongoHost: localhost
mongoPort: 27017
databaseName: jmeter_test
collectionName: blazemeter_tutorial

2.给你的测试计划添加一个线程组。

>右击->添加->线程(用户)->线程组

在以下步骤中,我们会考察我们的样例以测试基本的MongoDB操作:

  • 连接到一个数据库
  • 创建一个文档
  • 读取该文档
  • 修改该文档
  • 删除该文档

所有的这些步骤都假设以前的操作执行成功,如果你在任何步骤遇到错误,我们会中断线程的执行以阻止进一步的错误。要这么做,我们需要设置“在一个样例错误后执行的操作”以在我们的线程组“停止线程”。

写一个JMeter MongoDB样例

3.在你的线程组添加一个JSR223。这些是你正在创建的MongoDB样例。

右击->添加->样例->JSR223样例

4.将样例命名为“Connect toDB”,在“Connecting JMeter to the MongoDB Database”区域放置代码,在样例中标记为1.

5.添加另一个JSR223样例,将其命名为“Write to the DB”,在“How to Create a Documentand Insert it into the MongoDB Database with JMeter”区域放置代码,在样例中标记为2.

6.添加另一个JSR223样例,将其命名为“Read from DB”,在“Querying Documents” 区域放置代码,在样例中标记为3.

7.添加另一个JSR223样例,将其命名为“Update the Document”,在“Updating a Documentin the Database”区域放置代码,在样例中标记为4.

8.添加另一个JSR223样例,将其命名为“Delete a Document”,在“Deleting Documentsfrom the Database”区域放置代码,在样例中标记为5.

9.添加一个查看结果树监听器。

右击->添加->监听器->查看结果树

运行脚本,在监听器中查看结果:

可以看到我们的“Connect to DB”样例已经成功的返回了一个“Connected to blazemeter_tutorial”响应。

“Write to a DB”样例返回了一个成功的“Document inserted”响应。

我们在响应中看到找到了请求的文档。

这种响应告诉我们文档已经被修改。

最后,我们看到文档被从数据库删除。

我们所有的样例完成了相关操作。

现在,为了评估我们的MongoDB配置的性能,我们可以增加线程的数量,增加文档和查询的数量和复杂度,使用简单的数据写监听器而不是查看结果树监听器,并从命令行运行我们的脚本。

尽管在这个例子中,我们使用了非常基础的配置;在你们的性能测试中,你应该使用一个适用于你项目的实际配置。而且,你的测试文档和查询应该类似于你在工作应用中的期望。

使用Java请求样例

在以前的例子中,我们使用JSR223样例评估到MongoDB的请求。你可以考虑使用一个Java请求样例来替换。我们可以用同样的方法访问一个数据库,并用于Java请求样例在Java类中操作文档。

而且,有一个类似于Morphia ODM(文档对象映象器)的框架,可以使创建文档更加简单。

正如我们刚刚看到了,使用JMeter样例操作MongoDB是很容易的。但记住,计划你的测试环境和测试数据是一个获取有用的的MongoDB配置性能分析非常重要的步骤,这一步无可替代。

使用BlazeMeter加载测试

一旦你创建了JMeter脚本,将其上传到BlazeMeter并在云上平滑的运行你的测试。使用SaaS接口去扩展和运行你的测试会更容易,和联盟合作,获得更高级的报告。

要了解更多,从这开始。只要把你的URL放在下面的盒子里,你的测试会在几分钟内开始。

译者

张冲

对软件工程、多媒体设计、数据库编程、程序设计方面有多年的工作经验。具有较强的网络管理知识和实践经验,现主要从事网络安全相关工作,兴趣是从事大数据分析工作。

原文发布于微信公众号 - Mongoing中文社区(mongoing-mongoing)

原文发表时间:2019-09-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券