1.读取效率低:
(基于字段已有索引的场景)
1.单表数据量很大时,直接做数据分片(分片模式);
2.数据量不是很大时,开启副本集读写分离(副本集默认读写操作都在master节点上);
// 添加 readPreference=secondaryPreferred 配置项开启读取偏好,其他几种偏好配置可自行百度
spring
data:
mongodb:
uri: mongodb://root:password@xxxx:10001,xxxx2:10001,xxxx3:10001/admin?readPreference=secondaryPreferred
database: testdb
3.客户端使用连接池,避免重复建立连接花费的耗时:
package com.xxx.config;
import com.mongodb.MongoClientSettings;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.autoconfigure.mongo.MongoPropertiesClientSettingsBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.concurrent.TimeUnit;
/**
* @author programmer
* @version 1.0
* @create 2023/6/25 15:51
* @description
* @since 1.0
**/
@Configuration
@EnableMongoAuditing //开启@CreatedDate @LastModifiedDate等注解
@EnableTransactionManagement //开启事务管理
public class MongoConfig {
@Bean
MongoClientSettings mongoClientSettings() {
MongoClientSettings.Builder builder = MongoClientSettings.builder();
builder.applyToConnectionPoolSettings(b -> {
// 允许的最大连接数。这些连接在空闲时将保留在池中。一旦池耗尽,任何需要连接的操作都将阻塞等待可用连接 默认: 100
b.maxSize(100);
// 最小连接数。这些连接在空闲时将保留在池中,并且池将确保它至少包含这个最小数量 默认: 0
b.minSize(5);
// 池连接可以存活的最长时间。零值表示寿命没有限制。超过其生命周期的连接将被关闭并在必要时由新连接替换
b.maxConnectionLifeTime(0, TimeUnit.SECONDS);
// 池连接的最大空闲时间。零值表示对空闲时间没有限制。超过其空闲时间的连接将被关闭并在必要时由新连接替换
b.maxConnectionIdleTime(60, TimeUnit.MINUTES);
b.maxWaitTime(2,TimeUnit.MINUTES); //连接超时时长
});
return builder.build();
}
@Bean
MongoPropertiesClientSettingsBuilderCustomizer mongoPropertiesCustomizer(MongoProperties properties, Environment environment) {
properties.setAutoIndexCreation(true); //开启注解创建索引
return new MongoPropertiesClientSettingsBuilderCustomizer(properties, environment);
}
/**
* 注入mongodb事务管理器,mongodb4.0之后支持在副本集下支持多文档事务,单实例不支持多文档事务
* 使用 @Transactional 在业务代码上开启事务
*/
//关闭事务,因为启用事务使用@Transactional注解时,由于设置了读写分离,偏好是读取从secondary,会报错,
// 可设置在使用mongoTemplate时设置读取偏好为primary,使用后恢复偏好,这里直接不启用事务了
// @Bean(name = "mongoTransactionManager")
// @Profile("prod")
// MongoTransactionManager transactionManager(MongoDatabaseFactory factory){
// return new MongoTransactionManager(factory);
// }
}
2.写入效率低:
(基于字段已有索引的场景)
1.单表数据量很大时,直接做数据分片(分片模式);
2.副本集模式下
2.1 批量更新,使用批量更新避免多次提交花费的网络连接耗时,代码示例:
BulkOperations bulkOperations = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, OSSFileData.class);
for (OSSFileData fileData : fileDataList) {
Query query = Query.query(Criteria.where("_id").is(fileData.getId()));
Update update = Update.update("e3BugId", fileData.getE3BugId())
.set("e3ProductId",fileData.getE3ProductId());
bulkOperations.updateOne(query, update);
}
return bulkOperations.execute().wasAcknowledged();
2.2 指定Write Concern,WriteConcern的枚举值可百度查看详细:
在MongoDB的更新操作中,Write Concern用于指定写操作的确认级别。Write Concern定义了更新操作成功所需的副本数量,以及写操作是否等待确认。通过适当设置Write Concern,可以平衡更新操作性能和数据的一致性。代码示例:
// 这里使用最低级别的,无需写入确认即返回更新请求,效率最高,存在数据丢失可能
mongoTemplate.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
BulkOperations bulkOperations = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, OSSFileData.class);
mongoTemplate.setWriteConcern(null);
for (OSSFileData fileData : fileDataList) {
Query query = Query.query(Criteria.where("_id").is(fileData.getId()));
Update update = Update.update("e3BugId", fileData.getE3BugId())
.set("e3ProductId",fileData.getE3ProductId());
bulkOperations.updateOne(query, update);
}
return bulkOperations.execute().wasAcknowledged();
3.写入效率低的问题排查流程:
因为插入都是在MongoDB副本集的Primary节点来进行的,那么我们只需要分析Primary节点所在Linux服务器的基本情况,就可以从全局上对这个问题有一个总体的了解,分析服务器的指标包含但不限于CPU使用率、负载、内存、磁盘容量、TCP连接数等。
1.使用db.serverStatus().connections采集一下当前Primary节点所在服务器的连接数,可以发现,连接数量,可查看连接数是否变化比较大;
2.使用top查看mongo进程占用的cpu和内存变化
3.使用df -h 查看磁盘存储使用
4.使用 iostat -dx -m 1 查看磁盘读写状态,io使用率
5.lsblk
命令来查看磁盘的类型:
这里是虚拟磁盘,io效率执行较慢,影响查询性能,需要使用好的SSD硬盘
[root@zhijia ~]# lsblk -o NAME,ROTA
NAME ROTA
sr0 1
vda 1
├─vda1 1
└─vda2 1
vdb 1
└─vdb1 1
6. 使用MongoDB的性能分析工具mongostat查看数据库状态(在mongo的安装bin目录下)
mongostat对这个副本集的Primary节点进行了分析,注意,因为插入只能在Primary节点操作,所以对Primary节点进行分析:
./mongostat --host localhost:27017 -u root -p 123456 --authenticationDatabase admin
./mongostat --host localhost:27017 -u root -p 123456 --authenticationDatabase admin
[11:20:18]insert query update delete getmore command % dirty % used flushes vsize res qr|qw ar|aw netIn netOut conn set repl time
[11:20:18] 262 34 *0 *0 2 2462|0 6.1 96.0 0 44.8G 15.4G 0|483 1|128 987k 604k 3639 comos_temp PRI 2021-08-03T11:20:19+08:00
[11:20:19] 299 32 *0 *0 0 2668|0 6.1 96.0 0 45.3G 15.4G 0|790 15|128 990k 607k 4141 comos_temp PRI 2021-08-03T11:20:20+08:00
[11:20:20] 250 28 *0 *0 4 2271|0 6.1 96.0 0 45.6G 15.4G 0|926 13|128 945k 732k 4397 comos_temp PRI 2021-08-03T11:20:21+08:00
[11:20:21] 303 26 *0 *0 0 2706|0 6.1 96.0 0 46.0G 15.4G 0|1226 13|128 1m 600k 4841 comos_temp PRI 2021-08-03T11:20:22+08:00
[11:20:22] 297 51 *0 *0 0 2570|0 4.8 96.0 1 46.4G 15.4G 0|1548 32|128 980k 558k 5231 comos_temp PRI 2021-08-03T11:20:23+08:00
[11:20:23] 306 32 *0 *0 0 2453|0 4.3 96.0 0 46.7G 15.4G 0|1815 9|128 901k 582k 5537 comos_temp PRI 2021-08-03T11:20:24+08:00
[11:20:25] 309 34 *0 *0 0 2608|0 3.3 96.0 0 47.0G 15.4G 0|2149 14|128 982k 577k 5842 comos_temp PRI 2021-08-03T11:20:25+08:00
对mongostat性能分析工具的结果进行分析:
- 前面4个列分别代表每秒增删改查操作的次数;
- getmore代表游标信息,
- command代表mongod副本集之间的交互命令,
- dirty是一个关键参数,代表的是mongodb内存中被更新的脏数据占比,它有两个临界值,5%和20%。这些脏数据需要落在磁盘上,如果占比不超过5%,那么MongoDB会每分钟刷盘,如果超过5%,MongoDB会启动主动刷盘,尽量让这个数字不超过5%,如果写入太快,导致dirty的值超过20%,MongoDB就会阻塞新请求,全力以赴的刷盘。
- used另一个关键参数,它代表系统分配给MongoDB的内存中,已经被占用的内存比例,MongoDB默认情况下,最大会占用系统内存的60%,例如我们在100G内存中,MongoDB默认最大可以占用60G内存,而当used值为50%的时候,代表此时分配给MongoDB的内存已经被占用了30G。它也有2个临界值80%和95%,在低于80%的时候,MongoDB会认为当前分配的内存还没有被完全使用,不会做太多干预;如果高于80%了,MongoDB会认为内存冗余量不足,就会触发内存数据清理动作,清理策略是依据LRU算法的,旧数据会被踢出内存;一旦高于95%,那么代表MongoDB数据库承受着巨大的写入压力,就会阻塞其他操作,全力以赴淘汰内存中的数据。
- qr和qw,代表目前正在排队(queue)的read和write请求数量。ar和aw,代表目前活跃(active)的read和write请求
- conn,代表连接数。
其他参数不重要,这里不赘述。
有了上述参数的基础,我们可以看到,我们的检测结果中,used值已经达到了96%,而qw的值高达1k~2k,这说明当前分配给MongoDB的内存几乎耗尽,MongoDB承受巨大的写入压力,在努力淘汰内存中的数据。
到这里,整个分析过程就结束了,接下来就是对症治疗了。
解决方案及改善效果:
分析出来问题所在之后,解决方案就很容易能够提出来了,我们查看服务器上的剩余内存,发现剩余内存比较充裕,那么给MongoDB分配更多的内存,就能够缓解这个问题。
要加内存,就得知道MongoDB中,哪个参数控制内存的分配,MongoDB 3.2 及以后,默认使用 WiredTiger 存储引擎,可通过 cacheSizeGB 选项配置 WiredTiger 引擎使用内存的上限,一般建议配置在系统可用内存的60%左右。
接下来就是扩大内存的命令了,我们把内存从15G扩容到40G:
解决方案及改善效果:
首先查询当前cache大小, 执行:
db.serverStatus().wiredTiger.cache['maximum bytes configured']/1024/1024/1024
由于服务已经启动,修改配置文件无法生效(线上无法重启服务),使用实时修改:
db.adminCommand( { "setParameter": 1, "wiredTigerEngineRuntimeConfig": "cache_size=40G"})
- 增加内存:尽可能让MongoDB的数据和索引都能放进内存。
- 优化硬盘:虽然SSD比HDD快,但如果有条件,上更好的SSD或者NVMe硬盘。
总结:
1、排查数据库响应慢的问题,可以从客户端、网络、数据库服务三个层面考虑;
2、如果确定是数据库服务器问题,不妨从服务器开始排查,先总览全局;
3、服务器层面没问题,查看数据库的基本信息,例如数据库容量、索引、日志等问题
4、借助第三方工具来对性能瓶颈进行分析
5、根据最终的问题结论确定解决问题的方案
排查流程参考:
MongoDB线上案例:一个参数提升16倍写入速度-腾讯云开发者社区-腾讯云 (tencent.com)