我从一个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仍然请求null,因此它最终创建了更多相同的订单。一个(不太好的解决方案(是使orderId在数据库中唯一,这会阻止创建具有相同orderId的订单,但无论如何都会尝试这样做,这会导致数据库中的空id增加。任何想法都将不胜感激。正如你所看到的,我试着添加"睡眠"功能,但没有成功。
您的数据库无法在下一个请求到达之前完成保存操作。该问题类似于Doggile效应或"Doggile"效应;高速缓存缓冲";。
这需要更多地思考你是如何构建问题的:换句话说;解决方案";将更加哲学化,可能与代码无关,因此您在StackOverflow上的结果可能会有所不同。
";睡眠;解决方案根本不是解决方案:无法保证数据库操作可能需要多长时间,也无法保证在另一个重复请求到达之前您可能需要等待多长时间。根据经验;睡眠;被部署为";解决方案";对于并发性问题,它通常是错误的选择。
让我假设两种可能的处理方法:
选项1:只写:即不要尝试";解决";这是通过在写入数据库之前先从数据库中读取数据来实现的。只需使通往数据库的管道尽可能保持静音并继续写入即可。例如,考虑一个";日志记录";只存储webhook向其抛出的任何内容的表——不要试图从中读取,只需不断插入(或追加(即可。如果你对一个特定的顺序得到了100次ping回复,那就顺其自然吧:你的表会把它全部记录下来,如果你最终为一个orderId
得到了100行,让其他下游进程担心如何处理所有重复的数据。据推测,Sequelize足够聪明(并且您的数据库支持任何进程锁定(,可以对操作进行排队并处理写重复。
如果您确实希望在orderId
上有一个唯一的约束,那么这里的upsert
操作会很有帮助(这似乎很明智,但您可能知道在特定设置中的其他注意事项(。
选项2:使用队列。这显然更复杂,所以仔细权衡您的用例是否证明了额外的工作是合理的。将webhook数据放入队列(例如先进先出FIFO队列(,而不是立即将数据写入数据库。理想情况下,您应该选择一个支持重复数据消除的队列,这样退出的消息就可以保证是唯一的,但这会推断状态,并且通常依赖于某种类型的数据库,这从一开始就是一个问题。
队列为您做的最重要的事情是序列化消息,这样您就可以一次处理一个消息(而不是同时启动多个数据库操作(。当您从队列中读取消息时,可以将数据追加到数据库中。如果webhook不断触发,并且有更多的消息进入队列,那也没关系,因为队列会强制它们排列成一个文件,并且您可以一次处理每个插入。您将知道每个数据库操作在进入下一条消息之前都已完成,因此您永远不会";"砰"的一声;DB。换句话说,在数据库前面放一个队列将允许它在数据库准备好时处理数据,而不是每当webhook调用时。
这里的队列思想类似于信号量所实现的。请注意,您的数据库接口可能已经在后台实现了一种队列/池,所以请仔细权衡这个选项:不要重新发明轮子。
希望这些想法有用。
你救了我的时间@Everett和@pril henig。我发现直接保存到数据库读取到的记录重复。如果您将记录存储到一个对象中,并一次处理一个记录,这对我帮助很大。也许我会分享我的解决方案,也许有些人会觉得它在未来有用。
创建一个空对象以保存成功请求
export const queueAllSuccessCallBack = {};
在对象中保存POST
请求
if (status === 'success') { // I checked the request if is only successfully
const findKeyTransaction = queueAllSuccessCallBack[client_reference_id];
if (!findKeyTransaction) { // check if Id is not added to avoid any duplicates
queueAllSuccessCallBack[client_reference_id] = {
transFound,
body,
}; // save new request id as key and the value as data you want
}
}
访问要保存到数据库中的对象
const keys = Object.keys(queueAllSuccessCallBack);
keys.forEach(async (key) => {
...
// Do extra checks if you want to do so
// Or save in database direct
});