Meteor Collection Hooks如何让after insert钩子在前一个执行完成后再运行
Meteor after.insert 钩子并发竞态问题修复方案
你遇到的逻辑异常核心原因是当前after.insert采用「先查询最新文档→判断状态→执行修改/插入」的非原子流程,高并发下多个插入触发的钩子会同时读取到同一份lastDoc旧数据,导致后续逻辑全部出错,和钩子本身是否同步无关。Meteor服务端的集合钩子默认就是同步执行的,无需额外改造同步性,只要解决并发竞态问题即可,可选方案如下:
方案1:改用MongoDB原子操作(优先推荐)
把依赖查询结果的判断逻辑改到数据库操作的查询条件里,由MongoDB保证单条更新/插入操作的原子性,完全规避并发问题:
- 先给
SecondCollection新增isActive字段,用来标记当前未完成(finishedAt=0)的活跃文档 - 调整后的钩子代码如下:
FirstCollection.after.insert(function (userId, doc) { // 原子更新当前活跃的未完成文档,有符合条件的文档才会更新成功 const updateCount = SecondCollection.update( { isActive: true, count: { $lt: 264 } }, { $set: { lastTx: doc.txNumber }, $inc: { count: 1 } } ); if (updateCount > 0) { // 原子判断计数是否到阈值,到了就标记为已完成 SecondCollection.update( { isActive: true, count: 264 }, { $set: { finishedAt: Date.now(), isActive: false } } ); return; } // 无活跃文档时,检查上一个完成的文档是否冷却满1分钟 const lastFinished = SecondCollection.findOne( { isActive: false }, { sort: { finishedAt: -1 }, limit: 1 } ); if (lastFinished && Date.now() - lastFinished.finishedAt < 60000) { return; } // 满足条件插入新的活跃文档 SecondCollection.insert({ startNumber: doc.txNumber, count: 1, duration: 264, lastTx: doc.txNumber, time: Date.now(), finishedAt: 0, isActive: true }); });
方案2:加分布式锁控制执行顺序
如果逻辑复杂度高没法改成原子操作,可以加锁保证同一时间只有一个钩子的逻辑在执行:
// 提前创建LockCollection用来存储锁记录 FirstCollection.after.insert(function (userId, doc) { // 尝试获取锁,锁自动过期时间可根据业务调整 const getLock = LockCollection.upsert( { lockKey: 'secondCollectionProcess' }, { $setOnInsert: { lockedAt: Date.now(), requestId: this._id } } ).numberAffected === 1; // 没拿到锁则延迟重试 if (!getLock) { Meteor.setTimeout(() => { // 重新触发当前处理逻辑即可 }, 50); return; } try { // 此处放你原来的业务逻辑,同一时间只会有一个实例执行 } finally { // 执行完成后释放锁 LockCollection.remove({ lockKey: 'secondCollectionProcess' }); } });
方案3:用队列串行处理所有插入事件
把所有FirstCollection的插入事件都投递到串行执行的队列中,保证前一个事件的处理逻辑完全跑完后再处理下一个,从根源上避免并发问题。单实例部署可以用内存队列,多实例部署可以用MongoDB实现的分布式任务队列。
内容的提问来源于stack exchange,提问作者shfury
相关产品推荐
相关产品推荐

