使用队列循环调用函数时的序列号生成异常问题
解决MongoDB并发场景下顺序编号重复问题
问题根源
你遇到的重复编号问题,本质是并发竞态条件:多个队列任务同时读取totalCountOfSavedDocs,此时它们拿到的是同一数值,各自加1后写入数据库,最终导致多个文档使用相同编号。
可行解决方案
1. 使用MongoDB原子自增序列(推荐)
创建专门的序列管理集合,通过MongoDB的原子操作获取唯一递增编号,彻底避免竞态问题。
步骤:
- 首先创建
counters集合,初始化序列文档:db.counters.insertOne({ _id: "orderNumber", sequence_value: 0 }); - 在
createOrder方法中,替换原有的编号生成逻辑,改用原子操作获取自增编号:// 获取并递增订单编号(原子操作) const counter = await db.collection('counters').findOneAndUpdate( { _id: "orderNumber" }, { $inc: { sequence_value: 1 } }, // 原子递增 { upsert: true, returnDocument: "after" } // 不存在则创建,返回更新后的文档 ); // 生成的订单编号 const orderNumber = counter.value.sequence_value; // 后续使用orderNumber保存订单文档 - 原理:
findOneAndUpdate是MongoDB的原子操作,执行时会锁定目标文档,直到操作完成。即使多个并发请求同时触发,也会依次获取递增的编号,不会重复。
2. 添加唯一索引兜底
为订单集合的编号字段添加唯一索引,即使极端情况下出现编号重复(比如代码逻辑疏漏),数据库层面会直接阻止重复数据写入,避免脏数据。
执行以下命令创建索引:
db.orders.createIndex({ orderNumber: 1 }, { unique: true });
在代码中捕获E11000重复键错误,可选择重试编号生成逻辑。
3. 串行化队列处理(低吞吐量场景可选)
如果业务对处理效率要求不高,可将队列改为串行处理模式,确保同一时间只有一个任务生成编号。比如使用Service Bus的**会话(Session)**功能,将所有订单消息分配到同一个会话中,队列会自动串行处理该会话内的消息。
修改消息发送代码,添加会话ID:
for await (let order of orders) { const msg: ServiceBusMessage = { body: JSON.stringify({ type: "order-create-single", data: order, req: reqObj, }), sessionId: "order-sequence-session" // 指定同一会话ID }; await sender.sendMessages(msg); }
注意:这种方式会降低并发处理能力,仅适合订单量较小的场景。
总结
优先选择原子自增序列+唯一索引的组合方案,既保证编号的唯一性与顺序性,又能维持队列的并发处理效率。
内容的提问来源于stack exchange,提问作者Vinayak Sharma
相关产品推荐
相关产品推荐

