MongoDB压力测试方法实践-jmeter
引言
本文章主要讲解不同场景下,可以使用的MongoDB压测方法。并主要介绍实际业务场景下,如何使用jmeter对MongoDB压测。
一、基准测试,无实际业务场景压测方法
1、使用YCSB工具压测
适用范围:仅对读写比例有要求,对具体插入内容无要求的压测场景。
压测方法:YCSB压测MongoDB
二、有实际业务场景压测方法
业务压测背景
税务数字账户整体业务场景中,存储纳税人主数据、记账明细数据及凭证数据使用的档案库为MongoDB,本次主要对MongoDB进行性能验证。
1、使用jmeter自带“MongoDB Source Config”、“MongoDB Script”组件进行压测(需修改jmeter.properties配置,该组件被jmeter禁用)
适用范围:MongoDB3.*版本,且表中不涉及分片。
压测方法:
(1)替换jmeter自带MongoDB驱动jar包
使用jmeter自带MongoDB驱动版本无法通过用户名/密码方式连接数据库,需替换原有驱动至mongo-java-driver2.12.*-2.14.3版本。
将下载的 mongo-java-driver-*.jar 包放到apache-jmeter-*/lib中,备份原来的 mongo-java-driver-2.11.3.jar 包。
附:mongo-java-driver-2.14.3下载地址:Download mongo-java-driver-2.14.3.jar file
(2)修改jmeter配置文件,解除界面MongoDB 取样器的限制
打开apache-jmeter-*/bin/ jmeter.properties,搜索“not_in_menu”,将 MongoDB 相关的元件入口从这个地方移除即可。
重启 jmeter,就可以在配置元件中,找到 MongoDB Source Config,在取样器下面,找到 MongoDB Script。
(3)编写压测脚本
在 MongoDB Source Config 中配置 Server Address List: 服务器 ip:端口 、在MongoDB Source 中自定义一个资源名
在 MongoDB Script 中配置 MongoDB Source 为上一步配置的资源名 Database Name 中填写数据名,并在script 中,写上mongo语句
插入:db.collection.insert()
查询:这里需要注意,直接使用db.collection.find()会返回报错,需要使用db.collection.find().toArray()。
2、使用jmeter写 groovy 脚本调用 MongoDB(推荐)
适用范围:MongoDB任意版本。
通用压测方法
(1)替换jmeter自带mongo驱动jar包
根据 MongoDB 服务器的版本,下载对应兼容的mongo-java-driver 版本,参考下表。
我这里使用的是 MongoDB 3.6版本,所以使用mongo-java-driver-3.8.2.jar版本。
将下载的 mongo-java-driver-*.jar 包放到apache-jmeter-*/lib中,备份原来的 mongo-java-driver-2.11.3.jar 包。
重启 jmeter。
附:mongo-java-driver-3.8.2下载地址:Download mongo-java-driver-3.8.2.jar file
(2)编写 groovy 脚本
在线程组下,新增 JSR223 Sampler, 语言选择 groovy {Groovy 3.0.7 / Groovy Scripting Engine 2.0}
插入:
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 {
// 建立数据库连接
MongoClient mongoClient = MongoClients.create("mongodb://xxxx:xxxxx@127.0.0.1:27017/lewis_test");
// 连接 数据库 lewis_test为数据库名
MongoDatabase database = mongoClient.getDatabase("lewis_test");
// 连接 数据集 lewis_test_1为数据集名
MongoCollection<Document> collection = database.getCollection("lewis_test_1");
// 构造数据
Document document = new Document("id", "111111")
.append("number", 15890764)
.append("number1", 12234)
.append("number2", 44321)
.append("number3", 112311)
.append("box", new Document("num", "01").append("name", "xxx"))
.append("box", new Document("num", "02").append("name", "xxx"))
.append("object", Arrays.asList(new Document("id", "1111111")));
// 插入1条数据
collection.insertOne(document);
return "Document inserted";
}
catch (Exception e) {
SampleResult.setSuccessful(false);
SampleResult.setResponseCode("500");
SampleResult.setResponseMessage("Exception: " + e);
}
查询:
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 com.mongodb.client.FindIterable;
import static com.mongodb.client.model.Filters.*;
import org.bson.types.ObjectId;
import org.bson.Document;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
try {
// 建立数据库连接
MongoClient mongoClient = MongoClients.create("mongodb://xxxxx:xxxxxx@127.0.0.1:27017/lewis_test");
// 连接 数据库 lewis_test为数据库名
MongoDatabase database = mongoClient.getDatabase("lewis_test");
// 连接 数据集 lewis_test_1为数据集名
MongoCollection<Document> collection = database.getCollection("lewis_test_1");
// 主表只涉及1条数据
Document result = collection.find(eq("id", "11111111")).first();
return "查询:" + result;
}
catch (Exception e) {
SampleResult.setSuccessful(false);
SampleResult.setResponseCode("500");
SampleResult.setResponseMessage("Exception: " + e);
}
进阶压测方法
由于上述脚本中,每次插入、查询操作都会新建一次连接,增加并发后,性能会因为新建连接耗时而影响,无法测试出真实数据。
因此,通过在线程组中添加事务控制器,编写连接数据库方法作为连接池;添加循环控制器,编写数据库操作方法产生压力的方法优化脚本。
(1)新建事务控制器,编写连接数据库方法作为连接池
在事务控制器中添加 JSR223 Sampler, 语言选择 groovy {Groovy 3.0.7 / Groovy Scripting Engine 2.0},将连接的返回数据存在公共变量中。
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 {
MongoClient mongoClient = MongoClients.create("mongodb://xxxx:xxxxxx@127.0.0.1:27017/lewis_test?maxPoolSize=100");
// 连接 数据库 lewis_test为数据库名
MongoDatabase database = mongoClient.getDatabase("lewis_test");
// 连接 数据集 lewis_test_1为数据集名
MongoCollection<Document> collection = database.getCollection("lewis_test_1");
//存储collection对象
vars.putObject("collection", collection);
return "Connected to lewis_test";
}
catch (Exception e) {
SampleResult.setSuccessful(false);
SampleResult.setResponseCode("500");
SampleResult.setResponseMessage("Exception: " + e);
}
(2)在事务控制器中新建循环控制器,编写操作数据库语句
在循环控制器中添加 JSR223 Sampler, 语言选择 groovy {Groovy 3.0.7 / Groovy Scripting Engine 2.0},引用“collection”变量。
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 com.mongodb.client.FindIterable;
import static com.mongodb.client.model.Filters.*;
import org.bson.types.ObjectId;
import org.bson.Document;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
try {
//获取连接对象collection
MongoCollection<Document> collection = vars.getObject("collection");
//主表只涉及1条数据
Document result = collection.find(eq("id", "111111111")).first();
return "查询:" + result;
}
catch (Exception e) {
SampleResult.setSuccessful(false);
SampleResult.setResponseCode("500");
SampleResult.setResponseMessage("Exception: " + e);
}
Q&A
压测方法 | 问题 | 后续操作/问题定位 | 结论 |
---|---|---|---|
使用jmeter组件对MongoDB进行压测 | mongo-java-driver版本不匹配,无法链接数据库,鉴权失败报错 | 更换更高版本mongo-java-driver尝试 | 使用mongo-java-driver2.12.*-2.14.3版本可以成功连接数据库 |
使用db.collection.find()方法查询失败 | jmeter 使用 MongoDB 的 Java 模型,因此它与 shell 有点不同 | db.collection.find().toArray()可以成功查询 | |
当需要插入的表带有分片件后,无法进行插入操作 | 由于mongo为3.6版本,需要对应的mongo-java-driver3.6.*及以上,但jmeter使用该版本无法正常工作。 | 放弃该压测方法!!该压测方法仅适用于:mongodb3.*,且表中不涉及分片。 | |
创建业务pod,jmeter压接口 | pod资源不足,压力会在pod而不是数据库 | 放弃该方法。 | |
使用YCSB压测 | 自定义分片的表中进行插入压测,会报错无此主键 | YCSB已经将插入的脚本写好,无法自定义插入、查询、删除、更新的内容 | 放弃该方法,不符合此次压测场景。该压测方法适用于:仅对读写比例有要求,对具体插入内容无要求的压测场景。 |
使用jmeter通过写 groovy 脚本对 MongoDB进行压测 | 一个方法里每次建立连接会产生大量耗时,压力无法给到数据库 | 在线程组中,将建立连接方法写到事务控制器中,将建立连接的对象存入变量中,然后使用循环控制器,获取连接对象,对数据库操作方法进行循环 | 每一个线程只连接一次数据库,问题解决。 |
插入数据的id需要递增,当并发量过大时,多线程同时抢一个count计数器,导致发压性能下降 | 当并发量过大,排在后面的线程提前结束需要新的数,但计数器需要先给他之前的线程分配数。也就是出现了锁。 | 不使用计数器,通过给id字段附随机数(很大范围)的方式实现,不会出现锁。问题解决。 |
相关文章
- MongoDB读写性能优化研究(mongodb的读写性能)
- MongoDB数据迁移实践:轻松让你完成迁移(mongodb的数据迁移)
- MongoDB空间索引:解锁无限空间搜索可能(mongodb空间索引)
- MongoDB开发:从入门到精通(mongodb开发语言)
- 限制MongoDB单文档大小限制:解决方案(mongodb单个文档)
- MongoDB中文档数据索引优化实践(mongodb文档索引)
- MongoDB的正确维护与优化(mongodb维护)
- MongoDB数据库性能监控实践(mongodb数据库监控)
- MongoDB实现分片技术,减轻存储压力(mongodb分片机制)
- MongoDB 用户管理实践:精简运维(mongodb用户管理)
- MongoDB在应用开发中的用武之地(mongodb应用设计)
- MongoDB:简单高可靠性的NoSQL数据库(mongodb可靠性)
- 云时代,MongoDB云数据库成就强大效能(mongodb云数据库)
- MongoDB:实践指南(mongodb实例)
- MongoDB带来的极大优势(mongodb优点)
- 如何设置 MongoDB 的连接字符串?(mongodb连接字符串)
- MongoDB 测试连接实践指南(mongodb测试连接)
- 利用MongoDB实现分布式存储的最佳实践(mongodb分布式存储)
- MongoDB数据库驱动编程实践(mongodb驱动包)
- MongoDB实现极致搜索引擎(mongodb搜索引擎)
- 简单易懂的Linux下MongoDB安装教程(linux安装mongodb)
- 如何停止MongoDB数据库服务?(mongodb停止)
- MongoDB在树莓派上的应用与实践(mongodb树莓派)
- MongoDB的深度清理,做到一尘不染(mongodb 清理)