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

2024-08-25 22:11:14 浏览数 (2)

前言

 有很多终端设备和应用系统之间需要通信,设备将自身的一些指标数据定时发送到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开发社区提出的关于这个问题的帖子,如果您了解具体原因,非常期待和感谢您的解答

0 人点赞