node.js - 如何同时处理多个发布请求,同时将其中一个保存在数据库中?
问题描述
我从 webhook 收到 n 个 post 请求(在每个 webhook 触发器上)。来自同一触发器的所有请求的数据都是相同的——它们都具有相同的“orderId”。我有兴趣只保存其中一个请求,因此在每个端点命中时,我都在检查这个特定的 orderId 是否作为我的数据库中的一行存在,否则 - 创建它。
if (await orderIdExists === null) {
await Order.create(
{
userId,
status: PENDING,
price,
...
}
);
await sleep(3000)
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
}
return res.status(HttpStatus.OK).send({success: true})
} catch (error) {
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({success: false})
}
}
else {
return res.status(HttpStatus.UNAUTHORIZED).send(responseBuilder(false, responseErrorCodes.INVALID_API_KEY, {}, req.t));
}
}
问题是在 Sequelize 设法将新创建的订单保存在数据库中之前(所有 n 个 post 请求都在 1 秒内到达 enpoint - 或更短),我已经从其他 n 个 post 请求中获得了另一个端点,而 orderIdExists 仍然 equels null,因此它最终会创建更多相同的订单。一个(不太好的解决方案)是使 orderId 在数据库中唯一,这可以防止创建具有相同 orderId 的订单,但无论如何都会尝试,这会导致数据库中的空 id 递增。任何想法将不胜感激。ps 如您所见,我尝试添加“睡眠”功能无济于事。
解决方案
您的数据库未能在下一个请求到达之前完成其保存操作。这个问题类似于Dogpile 效应或“缓存猛击”。
这需要更多地考虑您如何构建问题:换句话说,“解决方案”将更具哲学性,并且可能与代码的关系较少,因此您在 StackOverflow 上的结果可能会有所不同。
“睡眠”解决方案根本不是解决方案:无法保证数据库操作可能需要多长时间,或者在另一个重复请求到达之前您可能等待多长时间。根据经验,任何时候将“睡眠”部署为并发问题的“解决方案”,通常都是错误的选择。
让我提出两种可能的处理方式:
选项1:只写:即在写入之前不要尝试通过从数据库中读取来“解决”这个问题。只要保持通向数据库的管道尽可能地笨拙并继续编写。例如,考虑一个“记录”表,它只存储 webhook 向它抛出的任何内容——不要尝试从中读取,只需继续插入(或更新插入)。如果您收到有关特定订单的 100 次 ping-backs,那就这样吧:您的表会记录所有内容,如果您最终得到 100 行的单个订单,请orderId
让其他下游流程担心如何处理所有重复的数据。据推测,Sequelize 足够智能(并且您的数据库支持任何进程锁定)来排队操作并处理重复写入。
如果您确实希望对 具有唯一约束,则此处的upsert
操作会很有帮助orderId
(这似乎很明智,但您可能会意识到特定设置中的其他注意事项)。
选项 2:使用队列。这显然更复杂,因此请仔细权衡您的用例是否证明了额外工作的合理性。不是立即将数据写入数据库,而是将 webhook 数据放入队列(例如先进先出 FIFO 队列)。理想情况下,您会希望选择一个支持重复数据删除的队列,以便保证现有消息是唯一的,但它会推断状态,并且通常依赖于某种数据库,这是一开始的问题。
队列为您做的最重要的事情是它会序列化消息,以便您可以一次处理一个消息(而不是同时启动多个数据库操作)。当您从队列中读取消息时,您可以将数据插入到数据库中。如果 webhook 不断触发并且更多消息进入队列,那很好,因为队列会强制它们全部排列单个文件,并且您可以一次处理每个插入。您将知道每个数据库操作在移至下一条消息之前已完成,因此您永远不会“猛击”数据库。换句话说,在数据库前面放置一个队列将允许它在数据库准备好时处理数据,而不是在 webhook 调用时处理数据。
这里的队列的想法类似于信号量完成的事情。请注意,您的数据库接口可能已经在后台实现了一种队列/池,因此请仔细权衡此选项:不要重新发明轮子。
希望这些想法有用。
推荐阅读
- flutter - 带有文本字段的 Flutter floatingActionButton 未完全位于键盘上方。需要像 facebook messanger 这样的东西
- vue.js - Nuxt generate 不会用内容填充生成的 HTML 文件
- javascript - 如何在反应js中响应猫头鹰轮播
- javascript - 如何将javascript中的字符串转换为可变字符串组合
- java - 如何显示不同的标题取决于动作?
- groovy - 从 Groovy 2.5 升级到 Groovy 3.0 后 OSGi 测试不再工作
- python - 如何删除 Seaborn FacetGrid 的图例标题?
- r - 如何将多个 fviz_dist 图组合成一个图
- python - 在 Azure Devops 发布管道中启动 Python 看门狗
- java - 将活动结果传递给正确的片段