首页 > 解决方案 > 如何同时处理多个发布请求,同时将其中一个保存在数据库中?

问题描述

我从 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 如您所见,我尝试添加“睡眠”功能无济于事。

标签: node.jsexpresssequelize.js

解决方案


您的数据库未能在下一个请求到达之前完成其保存操作。这个问题类似于Dogpile 效应或“缓存猛击”。

这需要更多地考虑您如何构建问题:换句话说,“解决方案”将更具哲学性,并且可能与代码的关系较少,因此您在 StackOverflow 上的结果可能会有所不同。

“睡眠”解决方案根本不是解决方案:无法保证数据库操作可能需要多长时间,或者在另一个重复请求到达之前您可能等待多长时间。根据经验,任何时候将“睡眠”部署为并发问题的“解决方案”,通常都是错误的选择。

让我提出两种可能的处理方式:

选项1:只写:即在写入之前不要尝试通过从数据库中读取来“解决”这个问题。只要保持通向数据库的管道尽可能地笨拙并继续编写。例如,考虑一个“记录”表,它只存储 webhook 向它抛出的任何内容——不要尝试从中读取,只需继续插入(或更新插入)。如果您收到有关特定订单的 100 次 ping-backs,那就这样吧:您的表会记录所有内容,如果您最终得到 100 行的单个订单,请orderId让其他下游流程担心如何处理所有重复的数据。据推测,Sequelize 足够智能(并且您的数据库支持任何进程锁定)来排队操作并处理重复写入。

如果您确实希望对 具有唯一约束,则此处的upsert操作会很有帮助orderId(这似乎很明智,但您可能会意识到特定设置中的其他注意事项)。

选项 2:使用队列。这显然更复杂,因此请仔细权衡您的用例是否证明了额外工作的合理性。不是立即将数据写入数据库,而是将 webhook 数据放入队列(例如先进先出 FIFO 队列)。理想情况下,您会希望选择一个支持重复数据删除的队列,以便保证现有消息是唯一的,但它会推断状态,并且通常依赖于某种数据库,这是一开始的问题。

队列为您做的最重要的事情是它会序列化消息,以便您可以一次处理一个消息(而不是同时启动多个数据库操作)。当您从队列中读取消息时,您可以将数据插入到数据库中。如果 webhook 不断触发并且更多消息进入队列,那很好,因为队列会强制它们全部排列单个文件,并且您可以一次处理每个插入。您将知道每个数据库操作在移至下一条消息之前已完成,因此您永远不会“猛击”数据库。换句话说,在数据库前面放置一个队列将允许它在数据库准备好时处理数据,而不是在 webhook 调用时处理数据。

这里的队列的想法类似于信号量完成的事情。请注意,您的数据库接口可能已经在后台实现了一种队列/池,因此请仔细权衡此选项:不要重新发明轮子。

希望这些想法有用。


推荐阅读