前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MongoDB高并发性能问题解决方案

MongoDB高并发性能问题解决方案

原创
作者头像
鱼找水需要时间
发布2024-08-25 22:11:14
1310
发布2024-08-25 22:11:14
举报
文章被收录于专栏:SpringBoot教程

前言

 有很多终端设备和应用系统之间需要通信,设备将自身的一些指标数据定时发送到mq队列中,应用系统将这些数据从队列中取出并按照相关协议解析后更新mongodb数据库(保存实时数据更新 不保存历史数据)。终端设备发送的数据类型较多 短时间内数据量过大,对系统后台解析能力有一定要求。

 由于短时间内数据量过大,一个队列一个消费者去监听,队列数据量过大,会导致消费者处理能力下降,从而影响整体系统性能。所以这里必然要采用多个消费线程去监听队列,保证同时并发处理数据。

 数据库方面,mongodb支持高并发,这一点是关系型数据库无法媲美的,下面是找到的一些性能对别数据,可以看一看:

比较 MongoDB 与 MySQL 以及性能测试

MongoDB mysql 性能压测 1亿数据对比

伪代码

代码语言:xml
复制
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.jms</groupId>
            <artifactId>javax.jms-api</artifactId>
        </dependency>

        <dependency>
            <groupId>com.ibm.mq</groupId>
            <artifactId>com.ibm.mq.allclient</artifactId>
            <version>9.0.5.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>javolution</groupId>
            <artifactId>javolution</artifactId>
            <version>5.5.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.48</version>
        </dependency>
    </dependencies>
代码语言:yaml
复制
ibm:
  mq:
    channel: JCMD.CHL
    host: 127.0.0.1
    password: 123456
    port: 1414
    queue-manager: JCMDOUTAM
    queuename: TESTQ
    receive-timeout: 5000
    username: mqm
spring:
  data:
    mongodb:
      #https://www.mongodb.com/zh-cn/docs/manual/reference/connection-string/#std-label-connections-connection-options
      uri: mongodb://localhost:27017/bjcmd?minPoolSize=5&maxPoolSize=100&maxIdleTimeMS=10000
config:
  #处理线程数,默认:10
  threadNum: 10

消费者代码

代码语言:java
复制
@Slf4j
@Component
public class MQListener extends MessageListenerAdapter {
    @Autowired
    JmsOperations jmsOperations;
    @Autowired
    private DataParseProc dataParseProc;

    @Override
    @JmsListener(destination = "${ibm.mq.queuename}",concurrency = "${config.threadNum}")
    public void onMessage(Message message) {
        try {
            if (message instanceof BytesMessage) {
                BytesMessage bytesMessage = (BytesMessage) message;
                byte[] data = new byte[(int) bytesMessage.getBodyLength()];
                bytesMessage.readBytes(data);

                if (null != data && data.length > 0) {
                    //解析数据,更新mongodb
                    dataParseProc.processingData(data);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }
}
代码语言:java
复制
@Slf4j
@Component
public class DataParseProc {
    private MongoTemplate mongoTemplate;
    public DataParseVersion(MongoTemplate template){
        mongoTemplate = template;
    }

    private void processingData(byte[] data) {
        long start = System.currentTimeMillis();

        mongoTemplate.updateFirst(query, buildUpdate(basDlV35), BA5.class, databaseA);
        mongoTemplate.updateFirst(query, buildUpdate(basDlV35), BA5.class, databaseB);
        mongoTemplate.updateFirst(query, buildUpdate(basDlV35), BA5.class, databaseC);
        //......updateFirst操作了8个不同的集合
    
        long end = System.currentTimeMillis();
        System.out.println("current thread: " + Thread.currentThread().getName() + ",consumption of time:" + (end - start) + "ms");
    }
}

模拟发送数据

代码语言:java
复制
@RestController
@RequestMapping("/send")
@Slf4j
public class MQSender {
    @Autowired
    JmsOperations jmsOperations;

    @GetMapping("/msg/mq")
    public void sendMq() {
        String dataStr = "00 00 01 00 CB 00 A3 03 00 00 01 01 0F 27 18 06 15 0C 0E 2E 00 00 00 00 00 00 00 00 00 00 00 00 5A 00 02 00 23 07 08 09 0A 0B 00 0A 00 00 0E 00 18 06 0C 08 03 38 0F 01 05 06 07 08 09 0A 0B 0C 0C 0D 0E 0F 01 02 03 04 05 06 08 09 0A 0B 0F 0C 0D 0E 0F 0F 01 02 03 04 05 06 07 03 04 05 06 07 08 09 0A 0B 05 0D 0E 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 01 02 03 01 04 05 23 00 04 00 21";
        byte[] data = DataParseUtil.hexStringToByteArray(dataStr);
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        for (int i = 0; i < 10000; i++) {
            jmsOperations.convertAndSend("TESTQ", data);
        }
        stopWatch.stop();
        System.out.println("MQSender over: " + stopWatch.getTotalTimeMillis());
    }
}

启动程序 调用发送接口,查看消费情况如下:

刚开始每个请求处理时间100ms,后边普遍到了400毫秒以上,开启10个线程,OPS(Operations Per Second 每秒钟系统能够处理的操作次数)算下来是20-30左右。由于批量操作的是不同的集合 没办法使用mongo的批量操作一次完成请求,程序必须从其他方面优化性能。

排查思路

 链路分为客户端、网络链路、服务器三个部分,任何一个环节出了差错,都会导致访问慢的问题,例如客户端部署的服务器负载高、网络链路带宽跑满、服务器上的慢查询等等问题,都可能表现为响应超时,所以,这里想说的是,如果你是一个开发,发现数据库返回时间超时了,先检查客户端和网络层面的问题,不要直接把问题都丢给DBA。

客户端连接池优化

 程序刚启动一个请求总共更新8次mongodb数据库花费时间100ms左右,后期随着并发数量增大时间随之到了500ms以上,可能是请求阻塞在了获取连接上,mongodb连接池数量太少 不够用。可以先试着调大几个重要参数试试。

连接参数选项

参数

描述

maxPoolSize

连接池中的最大连接数。 默认值为 100。

minPoolSize

连接池中的最小连接数。默认值为 0。

maxConnecting

池可以同时建立的最大连接数。默认值为 2。

maxIdleTimeMS

连接在池中可保持空闲状态的最大毫秒数,在此时间过后,连接将被删除或关闭。并非所有驱动程序都支持此选项。

<font color="red">最后发现对性能提升没有什么用。</font>

写入策略(WriteConcern)

写入策略是指当客户端发起写入请求后,数据库什么时候给应答,mongodb有三种处理策略:

  • 客户端发出去的时候,
  • 服务器收到请求的时候,
  • 服务器写入磁盘的时候
代码语言:java
复制
    private MongoTemplate mongoTemplate;
    public DataParseVersion(MongoTemplate template){
        //设置非应答式写入
        template.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
        mongoTemplate = template;
    }

也可以在连接字符串指定写入策略。例如:

代码语言:sh
复制
mongodb://localhost:27017/bjcmd?minPoolSize=5&maxPoolSize=100&maxIdleTimeMS=10000&w=0

连接字符串

写关注说明

<font color="red">最后发现还是对性能提升没有什么用。</font>

网络延迟

由于mongo安装在我本地电脑上,所以不涉及网络层面的延迟,这方面可以忽略。

<hr>

上面排查了客户端和网络链路问题都没有得到解决,剩下问题可能出现在服务端 也就是mongo数据库上,我们从以下几个方面查起

mongostat分析

我的mongodb安装在windows环境下:如果你的mongo安装目录bin下没有mongostatmongotop命令,可以到官网下载mongodb-database-tools安装包,解压后将bin目录下的文件复制到mongodb安装目录的bin目录下执行即可。

参数解释

可以看到 update 操作OPS只有200个左右,其它数据没有发现有异常的地方

mongotop分析

上图显示的是写入操作每秒内平均耗时,可以分析出来时间确实阻塞在了mongodb数据库上。

以上两个命令需要先执行命令启动监控,然后启动你的解析程序操作数据库,命令窗口每秒会刷新监控到的数据。

MongoDBCompass

MongoDBCompass是官方的一个分析工具,可以查询、分析mongodb数据库。

MongoDB锁分析

高并发一般会产生锁竞争,

MongoDB 中的锁分析: https://www.cnblogs.com/ricklz/p/17791076.html

磁盘性能

磁盘 I/O:大量数据插入会导致频繁的磁盘写入操作,可能会成为性能瓶颈。磁盘 I/O 的延迟和吞吐量直接影响数据插入的速度。

索引优化

MongoDB 的索引是为了提高查询性能而创建的,但在插入大量数据时,会增加索引的维护成本。每次插入数据后,MongoDB 都需要更新相应的索引,这可能导致性能下降。

mongoDB的索引详解

mongo.conf配置文件

mongo.conf 文件中的多个配置选项可以影响 MongoDB 的读写性能。报错存储引擎、日志记录、缓存大小等等。

配置文件

最后解决方案

由于平常在本地开发习惯使用了Debug模式启动 方便调试,一次偶然的机会使用Run模式启动,瞬间发现了新大陆,在Run模式下操作mongodb耗时正常,但是在Debug模式下启动耗时要消耗10倍的时间。

OPS可以3000左右。

可以看到在Run模式下操作mongo响应非常快,但是在Debug模式下耗时平均到了300ms左右。

可能的原因:在调试模式下可能会触发一些额外的操作消耗额外的时间,但是为什么使用命令查看请求时间都阻塞在的mongodb数据库上呢?(<font color=blue>等待排查具体原因</font>)

我在MongoDB开发社区提出的关于这个问题的帖子,如果您了解具体原因,非常期待和感谢您的解答

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 伪代码
  • 排查思路
    • 客户端连接池优化
      • 写入策略(WriteConcern)
        • 网络延迟
          • mongostat分析
            • mongotop分析
              • MongoDBCompass
                • MongoDB锁分析
                  • 磁盘性能
                    • 索引优化
                      • mongo.conf配置文件
                        • 最后解决方案
                        相关产品与服务
                        云数据库 MongoDB
                        腾讯云数据库 MongoDB(TencentDB for MongoDB)是腾讯云基于全球广受欢迎的 MongoDB 打造的高性能 NoSQL 数据库,100%完全兼容 MongoDB 协议,支持跨文档事务,提供稳定丰富的监控管理,弹性可扩展、自动容灾,适用于文档型数据库场景,您无需自建灾备体系及控制管理系统。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档