MongoDB读写效率问题

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)

修改mongodb的缓存大小 - leihongnu - 博客园 (cnblogs.com)

监控Mongo慢查询 - ExplorerMan - 博客园 (cnblogs.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值