node.js + mongo +多个实体的原子更新=头痛

时间:2012-09-05 02:04:56

标签: node.js mongodb concurrency

我的设置:

  1. Node.js的
  2. Mongojs
  3. 一个包含两个集合的简单数据库 - 库存和发票。
  4. 用户可以同时创建发票。
  5. 发票可能涉及多个库存物品。
  6. 我的问题:

    保持库存完整性。想象一下,一个场景是两个用户提交了两个重叠项目集的发票。

    一个天真(和错误)的实现将执行以下操作:

    1. 对于发票中的每个项目,请阅读清单集合中的相应项目。
    2. 修正库存物品的数量。
    3. 如果任何商品数量低于零 - 请将相关消息的请求放弃给用户。
    4. 保存库存物品。
    5. 保存发票。
    6. 显然,这种实现很糟糕,因为两个用户的动作将相互交错并相互影响。在典型的阻塞服务器+关系数据库中,这通过复杂的锁定/事务方案来解决。

      解决这个问题的点头+ mongoish方式是什么? node.js平台是否有为这类工具提供的工具?

1 个答案:

答案 0 :(得分:5)

您可以查看使用MongoDB的两阶段提交方法,或者您可以完全忘记事务并通过服务总线方法解耦您的流程。以亚马逊为例 - 他们将允许您提交订单,但他们不会确认它,直到他们能够保护您的库存物品,收取您的卡等等。这不会发生在一次交易中 - 这是一个可以单独进行的一系列步骤,并且可以在必要时应用补偿步骤。

一个天真的总线实现将执行以下操作(请记住,这只是一个通用的建议供您使用,确切的实现将取决于您对并发的特定需求等):

  1. 将订单放在队列中。此时,你可以 继续让你的客户等待,或者你可以感谢他们的 订购并让他们知道他们将收到一封电子邮件 处理。
  2. “库存工人”将获取订单并锁定库存 需要保留的物品。这可以通过许多不同的方式完成 方法。使用Mongo,您可以创建一个每个orderid都有一个文档的集合。该文件的库存物品ID和合理的TTL作为其ID (比方说30秒)。只要工人有锁,那就可以 管理其锁定的项目的库存水平。一旦它 做了改动,它可以删除“锁定”文件。
  3. 如果另一名工作人员想要管理同一项目 当它被锁定时,你可以将被阻止的工作者置于睡眠模式 X秒,然后重试,或者更好的是,你可以把它 请求返回消息总线,以便稍后由另一个接收 工人。
  4. 一旦工人解决了所有库存物品,它就可以了 在服务总线上放置另一条指示卡的消息 应该收费,或者处理应该收到通知 拉动库存,或者可以将电子邮件发送给制作者 订单等等。
  5. 听起来很复杂,但是一旦你有一个消息总线设置,它实际上相对简单。 A list of Node Message Bus Implementations can be found here.

    有些开发人员甚至会完全跳过正式的消息总线并使用数据库作为他们的消息传递引擎,这可以在简单的实现中工作。 Google Mongo和Queues。

    如果您不期望超过1台服务器且消息总线实现过于庞大,节点可以为您处理锁定和消息传递。例如,如果您确实想要使用节点锁定,则可以创建一个存储库存项目ID的数组。坦率地说,我认为消息总线是最好的方式。无论如何,这里是我过去使用的一些代码,用于处理与Node的简单外部资源锁定。

    // attempt to take out a lock, if the lock exists, then place the callback into the array.
    this.getLock = function( id, cb ) {
    
            if(locks[id] ) {
                locks[id].push( cb );
                return false;
            }
            else {
                locks[id] = [];
                return true;
            }
        };
    
    // call freelock when done
     this.freeLock = function( that, id ) {
                async.forEach(locks[id], function(item, callback) {
                    item.apply( that,[id]);
                    callback();
                }, function(err){
                    if(err) {
                      // do something on error
                    }
    
                    locks[id] = null;
    
                });
            };