前言
有很多终端设备和应用系统之间需要通信,设备将自身的一些指标数据定时发送到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有三种处理策略:
- 客户端发出去的时候,
- 服务器收到请求的时候,
- 服务器写入磁盘的时候
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下没有mongostat
和mongotop
命令,可以到官网下载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开发社区提出的关于这个问题的帖子,如果您了解具体原因,非常期待和感谢您的解答