2013-09-20 48 views
1

这可能是一个不同的坏主意,或者我们必须解决的一个数据库并发问题。MongoDB数据库信号量和Node.js Process.NextTick()

我们有一个被调用来更新mongo记录的方法。我们看到一些并发问题 - 进程A读取记录,进程B读取记录,进程A制作mods并保存记录,进程制作B mods并保存记录。因为B在A之后读取,所以在A写入之前,它不知道A所做的更改,并且我们丢失了来自A的数据。我想知道我们是否不能使用数据库信号量,集合,这是一个布尔值。如果我们在方法开始时读取记录,并且该字段为真,则正在编辑它。此时,使用process.nexttick()重新调用该方法,并使用相同的数据。否则,设置信号量,并继续。

读取和保存之间仍然会有一段时间,但它应该比我们现在要做的更快。

是这样的。任何想法,任何人都做过这样的事情?它会工作吗?

function remove_source(service_id,session, next) 
{ 
    var User = Mongoose.model("User"); 

    /* get the user, based on the session user id */ 
    User.findById(session.me,function(err,user_info) 
    { 
     if (user_info.semaphore === true) 
     { 
       process.nextTick(remove_source(service_id,session,next)); 
     } 
     else 
     { 
       user_info.semaphore = true; 
       user_info.save(function(err,user_new) 
       { 
        if (err) next(err,user_new); 
        else continue_on(null,user_new); 
       }); 
     } 

     function continue_on(user_new) 
     { 
      etc....... 
     } 

编辑:新代码:

功能现在看起来如下。我正在对阵列进行单独更新。这当然意味着如果第一次和第二次事务之间的事务失败,我现在有可能使数据不同步。我在想,我可以简单地重新保存在进入函数时检索到的用户对象,覆盖我的更改。我不知道,如果我没有改变这个对象,Mongoose/Mongo是不会做这个保存的,我们不得不去试试看。还有什么想法?

var User = Mongoose.model("User"); 

/* get the user, based on the session user id */ 
User.findById(session.me,function(err,user_info) 
{ 
     if (err) 
     { 
      next(err,user_info,null); 
      return; 
     } 

     if (!user_info || user_info.length === 0) 
     { 
      next(_e("ACCOUNT_NOT_FOUND"),"user_id: " + session.me); 
      return; 
     } 

     var source_service_info = _.where(user_info.credentials, {"source_service_id": service_id}); 
     var source_service = source_service_info.source_service; 

     User.findByIdAndUpdate(session.me,{$pull: {"credentials": {"source_service_id": service_id}}},{},function(err,user_credential_removed) 
     { 
      if (err) 
      { 
       next(err,user_info,null); 
       return; 
      } 

      User.findByIdAndUpdate(session.me,{$pull: {"criteria": {"source_service": source_service}}},{},function(err,user_criteria_removed) 
      { 
       if (err) 
       { 
        next(err,user_info,null); 
        return; 
       } 

       else 
       { 
        next(null,user_criteria_removed); 
       } 
      }); 
     }); 
}); 

};

+0

您可能只是想为您的文档添加版本,并根据读取后版本未更改的版本进行有条件更新。如果它未能更新,您会知道自上次读取以来进行了修改。 – WiredPrairie

回答

1

您的方法存在的问题是,它只是缩短了数据可以被第二个进程读取的时间,但并不能消除问题。

解决方法是将您的信号设置为与读取操作相同的操作。我没有使用Mongoose,但是在MongoDB中,如果信号量为false,则可以使用findAndModify返回用户记录;如果为false,则在一个原子操作中,将信号量设置为true。

如果您不想使用findAndModify,只有在未设置信号量的情况下,才可以首先进行更新,将信号量设置为true(或指定某个特定的ID值,以便知道它是您的信号量)。然后,如果该过程成功,您可以执行查找(可能会将您的信号量标识作为查找条件)。但是,findAndModify(如果它在Mongoose中可用)将一步完成此操作。

此处描述了一种变体:http://docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations/其中您执行一种乐观锁定形式,在将这些旧值更改为新值之前检查旧值是否保持不变。

存在使用一个单独的表来模拟两相在这个变化承诺:http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/

+0

这是我的想法,在我看后。这并没有让我们买得太多。这是我们与芒戈的第一个大项目。尽量保证它尽可能安全地进行多项交易似乎是设计中最丑陋的部分。我喜欢你这样做的方式,这是有道理的。我想我们必须从查找和修改中测试结果。我很确定它在猫鼬那里。 – CargoMeister

+0

我喜欢这种简单。有一件事仍然困扰着我,如果某件事失败了,记录就会被锁定。可能能够通过try/catch块来处理,或者只是在下面的错误检查中。 – CargoMeister

+0

如果事务安全性至关重要,MongoDB将会是一些额外的工作。正如Kyle Banker在Mongo In Action http://www.manning.com/banker2/中所说的,没有人会在MongoDB中建立银行的后端。 – nachbar

1

编辑:在下面的交汇处,这似乎是一个模式和更新的问题。问题可能会变成这样:我在数组中有一些条目,并且这些条目的序号索引也与其他一些数组相关。如何在不失配的情况下执行删除操作?

取决于现实世界中的频率与QA测试场景中出现的三种可能性。

  1. 考虑添加已删除的标志,但保持记录的顺序相同。如果有人切换,重复使用相同的记录,但无论您想要如何修复。
  2. 对每个元素使用关联数组(JS对象)(而不是关系世界的特征)。如果您需要订单,请添加按顺序列​​出键的数组。两者都有更新的语法,而不用触及任何其他变化的内容,也不会覆盖对不同字段的更改。
  3. 使用键是数字的关联数组。实际删除不会影响检索。

    东西= {} 东西[1] = {一些: '细节'} 东西[2] = {一些: 'details2'}

当时 1)你在修改同一个领域?将其作为一个数组,并推送更改,并弹出最新的值以读取当前值。

2)你是否在改变不同的领域,但数据正在遭受挫折?然后有更好的语法用于更新。你可以逐场更新。

$set: { 'fielda': 'valuea' } 

不会失去以前的领域

3)编辑可以改变你的架构

4)改变的过程的时间,使他们不重叠。或者他们可以在较小的子集中这样做,以防止重叠。

我想知道,出于兴趣,需要多个进程来更新相同的记录?我不会用任何看起来像这样的东西。

+0

客户端是一个ipad客户端,它发送一个领带的多个更改。我们已经将客户所拥有的物品构建到用户集合中,因为我们经常更改关于用户的信息以及他们同时创建的物品。该文件有许多子文件。它使更新更安全,然后将其分开。特别是在这种情况下,它是来自各种外部服务的证书 - 他们可能决定放弃登录。客户同时发送它们,而不是一个接一个地发送出去。我们可能会将它们作为单个数组发送。 – CargoMeister

+0

删除现有凭证的用例不需要存在交叉更新冲突 - 它们在用户记录中的某个时刻是不同的。这听起来像是你正在树中对该字段进行更新。向我们展示更新,这是一个快速解决方案。 –

+0

嗨,吉姆,是的,我们正在保存整个用户对象。凭证位于数组中,然后与它们关联的对象位于另一个数组中。我们正在操作这些数组,然后更新用户对象,然后重新保存整个用户对象。从你所说的话看,这可能不是最好的策略。但是,由于它们是数组,我们正在删除成员,我可以删除数组中的单个成员吗?我们也喜欢在完成时压缩数组,以便我们有连续的编号,而不是留下空白。所以,编号可以在保存之间改变。 – CargoMeister