MQTT的on_message回调中Mongoose查询执行耗时过长如何解决
问题原因
- 核心问题出在
forEach搭配async/await的错误用法:Array.forEach不会等待异步回调执行,代码运行时会把循环里的所有数据库查询瞬间全部发起,不会按顺序等上一个查询结束再跑下一个。你的示例里一次传入6个查询条件,会直接发起6条并发查询,而Mongoose默认的连接池最大空闲连接只有5个,超出的查询必须排队等待其他查询释放连接,额外增加了大量等待时间,表现出来就是查询耗时极长。把查询移到回调外部单独执行时,没有并发争抢连接的情况,单条查询可以直接拿到连接执行,自然速度正常。 - 额外隐患:mqtt.js的
message事件回调本身不感知异步逻辑,你传入async函数后,客户端不会等回调里的异步操作执行完就会继续处理下一条投递过来的消息,消息量上来之后会堆积大量待执行的数据库请求,进一步拖慢整体查询速度。
修复方法
优先用批量查询替代循环单查,性能提升最明显:把多次单条件查询合并为一次$in查询,只需要一次数据库IO就能拿回所有匹配结果,从根源上减少连接占用。如果业务确实需要逐条查询,替换forEach为支持异步等待的普通for...of循环,控制查询并发度,避免打满连接池。
修复后的参考代码:
var mqtt = require('mqtt'); const mqttClient = mqtt.connect("mqtt://broker.emqx.io:1883",{clientId:"mqttClient", keepalive: 1000}); const options = { retain:false, qos:1 }; mqttClient.subscribe('topic' ,{qos:1}); mqttClient.on('message', async function (topic, message){ const msg = JSON.parse(message); // 批量查询方案:性能最优,一次查询拿回所有匹配数据 const devList = await Device.find({ letters: { $in: msg } }).lean(); // 需要按查询条件对应结果的话,在内存做一次分组映射即可 const devGroupMap = new Map(); devList.forEach(dev => { devGroupMap.set(dev.letters, dev); }); // 逐条查询方案(按需使用):用for...of替代forEach,串行执行等待查询完成,避免无控制并发 // for (const letter of msg) { // const dev = await Device.find({ letters: letter }).lean(); // // 单条结果对应的业务逻辑写在这里 // } }); mqttClient.publish('topic', JSON.stringify(['a', 'b', 'c', 'd', 'e', 'f']), options);
额外优化项
- 消息量级较高的场景,可以在Mongoose连接MongoDB时适当调整连接池大小,根据数据库实际承载能力设置
maxPoolSize参数,不要用默认的过小配置:
mongoose.connect('你的MongoDB连接地址', { maxPoolSize: 20 });
- 如果消息生产速度长期高于消费速度,可以增加消费并发控制逻辑,限制同一时间内正在执行的数据库查询数量,避免数据库被瞬间打垮。
内容的提问来源于stack exchange,提问作者Nay Oo Lwin
相关产品推荐
相关产品推荐

