同步多个请求和多个数据库调用
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(...);
有两个异步调用findOne
和save
。我面临的问题是在一个异步调用期间(即在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
因此,对于这两个请求,应用程序都认为用户不存在,因此尝试使用相同的密钥创建文档两次。
如何解决?
嗯,首先,我已将findOne
和save
之间的时间减到最少。但是,在某些情况下问题仍然存在(可能只有 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()
。我回答了这对于“并发”系统来说是错误的方法。这就是我的观点。以上是关于同步多个请求和多个数据库调用的主要内容,如果未能解决你的问题,请参考以下文章
工厂模式实现并发请求多个接口 (同步后台数据实现离线APP)
工厂模式实现并发请求多个接口 (同步后台数据实现离线APP)