我目前正在处理一个问题,当我在 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
因此,对于这两个请求,应用程序都认为该用户不存在,因此尝试使用相同的 key 创建文档两次。
如何解决?
首先,我最大限度地缩短了
findOne
和save
之间的时间。然而,这个问题在某些情况下仍然会发生(可能只有 1/1000)。我不想使用 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 */
}
或者,我是否以错误的方式处理这个问题。有什么想法吗?
最佳答案
简单。 不要直接使用.findOne()
和.save()
,而只是.update()
数据库中的对象。
MongoDB 和“锁定”(以这种方式)不会发生,这基本上是设计使然。你不应该:
- 预订一本书
- 从书架上拿书
- 在书中写入新段落
- 将书放回书架
- 取消预订书籍供其他人使用
这是“可扩展模式中的一个巨大NO”。
所以而不是你只是这样做。
"Write new paragraph to book on shelf"
最好
"Write new paragraph to book on shelf, where the current paragraph is what I expect it to be".
这应该是一个“明确”的类比。
所以这样做:
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
}
);
不会触及对象的是“updated”的当前值“大于”您想要设置的值。
纯逻辑。
关于node.js - 同步多个请求和多个数据库调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31473123/