如何确保两个用户可以在mongodb

时间:2016-04-21 23:10:38

标签: multithreading mongodb transactions mongodb-query

我有一个名为Transaction的模型,它具有以下模式

var transactionSchema = new mongoose.Schema({
    amount: Number,
    status: String,
    _recipient: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
    _sender: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
});

我希望此交易的发件人和收件人能够“确认”交易发生。 status以“初始”开头。因此,当只有发件人确认了交易(但收件人却没有)时,我想将status更新为“senderConfirmed”或其他内容,当收件人确认了它(但发件人没有)时,我想要将状态更新为“recipientConfirmed”。当他们两者确认它时,我想将状态更新为“完成”。

问题是,我怎么知道何时以避免竞争条件的方式将其更新为“完成”?如果发件人和收件人同时确认交易,则两个线程都会认为状态为“初始”并将其更新为“senderConfirmed”或“recipientConfirmed”,而实际上它应该“完成”

我读了MongoDBs两阶段提交方法here,但这不太符合我的需要,因为我没有(在另一个线程当前正在修改事务的情况下)想要阻止第二个线程从进行更新 - 我只是希望它等待直到第一个线程完成更新之前,然后根据事务的最新状态使其更新内容。

2 个答案:

答案 0 :(得分:2)

底线是您需要“两个”更新语句分别为每个发件人和收件人执行此操作。因此,基本上一个将尝试设置“部分”状态以完成,而另一个将仅将“初始”状态匹配设置为“部分”状态。

批量操作是实现多个语句的最佳方式,因此您应该通过访问底层驱动程序方法来使用它们。现代API版本具有.bulkWrite()方法,如果服务器版本不支持“批量”协议,则会很好地降级,并且只是回退发布单独的更新。

// sender confirmation
Transaction.collection.bulkWrite(
    [
       { "updateOne": {
           "filter": {
               "_id": docId,
               "_sender": senderId,
               "status": "recipientConfirmed"
           },
           "update": {
               "$set": { "status": "complete" }
           }
       }},
       { "updateOne": {
           "filter": {
               "_id": docId,
               "_sender": senderId,
               "status": "initial"
           },
           "update": {
               "$set": { "status": "senderConfirmed" }
           }
       }}
    ],
    { "ordered": false },
    function(err,result) {
       // result will confirm only 1 update at most succeeded
    }
);

当然,除了不同的状态检查或更改之外,同样适用于_recipient。您可以在$or_sender上发出_recipient条件,并具有通用的“部分”状态,而不是编码不同的更新条件,但同样适用基本的“两次更新”流程。

当然,你再次“可以”只使用常规方法并以另一种方式向服务器发出两个更新,甚至可能并行,因为条件仍然是“原子”,但这也是{{1}的原因。 }选项,因为它们没有确定的序列需要在这里得到尊重。

批量操作虽然比单独的调用更好,因为发送和返回只是一个请求和响应,而不是每个“两个”,因此使用批量操作的开销要小得多。

但这是一般方法。在另一方发出确认之前,任何单一陈述都不可能在“僵局”中留下“状态”或标记为“完整”。

在第一次尝试更新和第二次尝试更新之间,状态从“初始”更改为“可能性”且非常小,这将导致无法更新任何内容。在这种情况下,您可以“重试”在后续尝试中“应该”更新的操作。

这应该只需要“一次”重试最多。而且很少见。

注意:在Mongoose模型上使用{ "ordered": false }访问器时应小心。所有常规模型方法都内置了逻辑,以“确保”与数据库的连接在它们执行任何操作之前实际存在,实际上是“排队”操作,直到存在连接。

将应用程序启动包装在事件处理程序中通常是一种很好的做法,以确保数据库连接:

.collection

因此,针对此案例使用mongoose.on("open",function() { // App startup and init here }) "on"事件。

通常,在触发此事件之后,或者在应用程序中已经调用任何“常规”模型方法之后,始终存在连接。

可能mongoose将在未来版本中直接在模型方法中包含"once"等方法。但目前它没有,所以.bulkWrite()访问器是从核心驱动程序中获取底层Collection对象所必需的。

答案 1 :(得分:0)

更新:我根据我的原始回复未提供答案的评论澄清了我的回答。

另一种方法是将状态跟踪为两个独立的属性:

senderConfirmed: true/false,
recipientConfirmed: true/false,

当发件人确认您只需更新senderConfirmed字段时。当收件人确认您更新recipientConfirmed字段时。他们无法互相覆盖。

要确定交易是否完整,您只需查询{senderConfirmed:true,recipientConfirmed:true}

显然,这是对文档架构的更改,因此可能不太理想。

原始答案

是否可以更改您的架构?如果您有两个属性senderStatusrecipientStatus怎么办?发件人只会更新senderStatus,收件人只会更新recipientStatus。然后他们无法覆盖彼此的变化。

我认为你还需要一些其他方法来将其标记为完整。你可以给我们一个cron工作或什么......