【发布时间】:2018-10-08 23:17:11
【问题描述】:
我需要 NodeJS 来防止相同请求的并发操作。据我了解,如果 NodeJS 收到多个请求,会发生这种情况:
REQUEST1 ---> DATABASE_READ
REQUEST2 ---> DATABASE_READ
DATABASE_READ complete ---> EXPENSIVE_OP() --> REQUEST1_END
DATABASE_READ complete ---> EXPENSIVE_OP() --> REQUEST2_END
这会导致运行两个昂贵的操作。我需要的是这样的:
REQUEST1 ---> DATABASE_READ
DATABASE_READ complete ---> DATABASE_UPDATE
DATABASE_UPDATE complete ---> REQUEST2 ---> DATABASE_READ ––> REQUEST2_END
---> EXPENSIVE_OP() --> REQUEST1_END
这就是它在代码中的样子。问题是应用程序开始读取缓存值和完成写入之间的窗口。在此窗口期间,并发请求不知道已经有一个具有相同 itemID 的请求在运行。
app.post("/api", async function(req, res) {
const itemID = req.body.itemID
// See if itemID is processing
const processing = await DATABASE_READ(itemID)
// Due to how NodeJS works,
// from this point in time all requests
// to /api?itemID="xxx" will have processing = false
// and will conduct expensive operations
if (processing == true) {
// "Cheap" part
// Tell client to wait until itemID is processed
} else {
// "Expensive" part
DATABASE_UPDATE({[itemID]: true})
// All requests to /api at this point
// are still going here and conducting
// duplicate operations.
// Only after DATABASE_UPDATE finishes,
// all requests go to the "Cheap" part
DO_EXPENSIVE_THINGS();
}
}
编辑
当然我可以这样做:
const lockedIDs = {}
app.post("/api", function(req, res) {
const itemID = req.body.itemID
const locked = lockedIDs[itemID] ? true : false // sync equivalent to async DATABASE_READ(itemID)
if (locked) {
// Tell client to wait until itemID is processed
// No need to do expensive operations
} else {
lockedIDs[itemID] = true // sync equivalent to async DATABASE_UPDATE({[itemID]: true})
// Do expensive operations
// itemID is now "locked", so subsequent request will not go here
}
}
lockedIDs 这里的行为类似于内存中的同步键值数据库。没关系,如果它只是一台服务器。但是如果有多个服务器实例呢?我需要有一个单独的缓存存储,比如 Redis。而且我只能异步访问 Redis。所以这行不通,很遗憾。
【问题讨论】:
-
为什么要更新数据库作为获取请求的一部分?此外,您所要求的将大大降低您的应用程序性能和可伸缩性。我 99% 确定您实际上并不想要您认为自己想要的东西。
-
数据库是指 Redis 缓存。我基本上想将 itemID 设置为
{processing: true},这样后续的请求就会知道这个操作已经在处理中,他们不需要自己处理。他们只会等到一个进程完成,然后他们才能在数据库中找到输出。 -
您还应该注意,您不应该让第二个请求等待第一个请求。您最终会遇到网关超时等问题。最好的办法是返回带有
{process_1_done: false}类消息的第二个请求,并让前端继续检查。 -
@nbwoodward 这就是我要解决的问题...第二个请求如何知道 process_1 已启动(阶段 0)、正在处理(阶段 1)或已完成(阶段2)?我试图通过保存
processing = true在第一个请求中做到这一点,因此当发出第二个请求时,它可以访问该值并知道 process_1 处于第 1 阶段。 -
我仍然不明白您为什么要使用获取请求启动更新过程。无论它是哪个过程或您在哪里处理它。