使用异步任务排队进行原子数据库写入

Atomic database write with async task enqueue

提问人:Alfred 提问时间:10/3/2023 最后编辑:Alfred 更新时间:10/4/2023 访问量:39

问:

如今,在微服务架构中,将面向用户的逻辑(同步部分)与后端处理(异步分离)分离是一种常见的做法,以提高服务吞吐量、可靠性和用户体验。例如,在电子商务网站中,通过首先验证订单,然后将其存储在数据库中,同时及时响应用户来接受订单。随后,订单处理(如履行)通过事件驱动的工作流服务在后端异步执行。

异步处理方面通常作为基于队列的处理系统实现,如 Celery 等任务队列。

现在,我的问题围绕着如何确保订单持久性和任务排队的原子性。如果没有原子保证,可能会出现几个问题:

  • 如果排队发生在数据库持久化之后。然后,如果服务在数据库持久性之后和任务排队之前崩溃,则该订单可能保持未处理状态。
  • 如果排队发生在数据库持久化之前,并且任务可能在数据库提交之前处理,则在数据库中可能找不到顺序。

我有兴趣了解在此类场景中建立异步处理的广泛接受的行业解决方案。

P.S. 值得注意的是,可以有一个守护进程,该进程会定期检查数据库中是否有新订单,并将它们排入任务队列。但是,这种方法通常被认为是反模式,因为它会给数据库带来不必要的负载,并且实质上将数据库视为队列。此外,如果数据库是分布式和分片的,这种方法可能会变得棘手。

谢谢!

异步 队列 微服务 任务 原子

评论


答:

1赞 Christophe Quintard 10/3/2023 #1

我建议你两种方法:

  • 使用变更数据捕获 (https://en.wikipedia.org/wiki/Change_data_capture),这是数据库在执行操作时发出的事件流。例如,PostgreSQL 公开其预写日志记录机制 (https://www.postgresql.org/docs/current/wal-intro.html)。通过使用 Debezium (https://debezium.io/documentation/reference/stable/connectors/postgresql.html) 之类的东西,您可以将这些事件流式传输到 Kafka 主题或 RabbitMQ 队列。

  • 首先在消息队列中写入,然后使用队列来更新数据库。您可以在写入消息后立即响应用户,并依靠重试来确信订单迟早会写入数据库。这就是“最终一致”的含义。你必须处理一个潜在的冲突:你检查你的库存,你为订单写一条消息,然后处理消息并更新库存。但与此同时,库存可能已经发生变化,订单无法处理。好吧,处理它,因为即使有完美的交易,这无论如何都会发生!库存并不总是可靠的:有些物品可能已经丢失或被盗,或者有人在盘点时犯了错误,而您刚刚出售的物品无法发货。为了补偿,您可以选择(您决定或您的客户决定):退款给客户,或等待更多库存到达并运送到期商品。

评论

0赞 Alfred 10/4/2023
谢谢!我还考虑了第二种方法。但是,这可能会影响用户体验,因为用户不会立即收到成功订单的确认。我一直在考虑的另一种方法是,因为我们只能在异步任务成功排队后响应用户。然后,如果服务器在写入数据库和将任务排队之间遇到问题,我们可以依靠客户端重试。只要服务是幂等的,客户端就可以根据需要重试。好奇这种方法的任何潜在缺点。
0赞 Christophe Quintard 10/4/2023
我已经完成了关于第二种方法的答案(这是我推荐的方法)。有太多的东西要补充,不能把它作为评论;-)