同步多个请求和多个数据库调用

Posted

技术标签:

【中文标题】同步多个请求和多个数据库调用【英文标题】:Synchronizing multiple requests and multiple database calls 【发布时间】:2015-10-07 00:22:15 【问题描述】:

我目前正在处理一个问题,当我在 node.js 应用程序中的 mongo 数据库(使用 mongoose)中创建文档时出现重复键错误。

这里是场景

更新特定用户的 HTTP 请求。

POST /process  "user": 123 

服务器检查用户是否存在。如果不存在,则创建新用户,否则更新用户并保存。

User.findOne( "user": 123 , function (err, doc) 
    if (!doc) 
        doc = new User( ... );
    
    doc.updated = Date.now();
    doc.save(...);

有两个异步调用findOnesave。我面临的问题是在一个异步调用期间(即在doc.save 完成第一个请求之前)第二个HTTP 请求(针对同一用户)时会发生什么。即使node.js 是单线程的,如果在异步 I/O 期间有一些延迟,这仍然会发生。

R1: POST /process
R1: findOne => async
R2: POST /process
R2: findOne => async
R1: !doc = true
R2: !doc = true

因此,对于这两个请求,应用程序都认为用户不存在,因此尝试使用相同的密钥创建文档两次。

如何解决?

嗯,首先,我已将findOnesave 之间的时间减到最少。但是,在某些情况下问题仍然存在(可能只有 1/1000)。

我不想使用 upsert,因为在创建新用户时,我还使用默认值设置了一些其他字段。我认为使用 upsert 会很棘手。

理想情况下,我想确保一次只有一个请求可以进入处理逻辑(有点像函数调用周围的互斥锁)。但是,我不想阻止请求调用 - 所以也许我可以使用一些不错的异步锁定实用程序?

例子:

/* don't block, call the function when a lock can be acquired */
lock(function (done) 
    /* can only enter here one at a time */
    done(); /* <-- unlocks */

或者,我是不是以错误的方式处理这个问题。有什么想法吗?

【问题讨论】:

【参考方案1】:

简单。 不要使用.findOne().save()直接.update()数据库中的对象。

MongoDB 和“锁定”(以这种方式)只是 不会发生,这基本上是 设计。您不应该:

    预订一本书 从书架上拿书 为书写新段落 将图书归还书架 取消保留图书以供他人使用

这是可扩展模式中的“伟大的NO

所以而不是你只是这样做。

“写新段落到书架上”

最好

“将新段落写到书架上,当前段落是我所期望的”

这应该是一个“清晰”的类比。

所以改为:

User.update(
    "user": 123 ,
    "$set":  "updated": new Date()  ,
   function(err,numAffected) 
       // do something in callback
   
);

其中“简单地”只是更新当前的“更新”日期,然后在请求时进行。

或者更好:

var updatedDate = new Date();

User.update(
    "user": 123, "updated":  "$lt": updatedDate  ,
    "$set":  "updated": updatedDate  ,
   function(err,numAffected) 
       // do something in callback
   
);

不会触及对象的是“更新”的当前值“大于”您要设置的值。

纯逻辑。

【讨论】:

如果文档尚不存在,这肯定行不通。因此,需要指定upsert。在这种情况下,我将如何“将这些字段设置为新文档的这些值”而不覆盖现有文档的这些字段。 @pbergqvist 当然。这是有效的。但我主要是写关于“更新”的纯粹情感。如果你需要创建然后你“upsert”。但问题被提出为.findOne(),然后是.save()。所以你不会 .findOne() 什么都不存在,现在是吗? 也许我应该在我的问题中说得更清楚,但是创建新文档是我面临的问题的关键。因此,在这种情况下,我正在寻找如何在使用 upsert 时设置默认字段。 其实,$setOnInsert 可以使用 upsert 解决问题 @pbergqvist 当然应该。我已经发过几次了。 $setOnInsert 仅出现在 upsert 上。但是示例中的$lt 条件也适用于这种简单的情况。无论如何,您询问了.findOne().save()。我回答了这对于“并发”系统来说是错误的方法。这就是我的观点。

以上是关于同步多个请求和多个数据库调用的主要内容,如果未能解决你的问题,请参考以下文章

从多个线程同步对共享对象的方法调用

Redis复制:主从同步

工厂模式实现并发请求多个接口 (同步后台数据实现离线APP)

工厂模式实现并发请求多个接口 (同步后台数据实现离线APP)

GWT:客户端过程和 rpc 请求总是被多次调用,具有多个线程 id

Servlet 似乎同步处理多个并发浏览器请求