与数据库和消息队列记录的最终一致性

时间:2017-02-05 15:04:53

标签: database domain-driven-design message-queue microservices eventual-consistency

我有一个应用程序,我需要将一些数据存储在数据库(例如mysql)中,然后在消息队列中发布一些数据。我的问题是:如果应用程序在数据库中存储后崩溃,我的数据将永远不会写入消息队列然后丢失(因此我的系统的最终一致性将无法保证)。 我该如何解决这个问题?

5 个答案:

答案 0 :(得分:13)

  

我有一个应用程序,我需要将一些数据存储在数据库(例如mysql)中,然后在消息队列中发布一些数据。我的问题是:如果应用程序在数据库中存储后崩溃,我的数据将永远不会写入消息队列然后丢失(因此我的系统的最终一致性将无法保证)。我该如何解决这个问题?

在这种特殊情况下,答案是从数据库加载队列数据。

也就是说,在用于写入数据的同一事务中,将需要排队的消息写入数据库。然后,异步地,您从数据库中读取该数据,并将其写入队列。

见Udi Dahan的Reliable Messaging without Distributed Transactions

如果应用程序崩溃,恢复很简单 - 在重新启动期间,您在数据库中查询所有未确认的消息,然后再次发送。

请注意,此设计确实希望消息的使用者设计为at least once delivery

答案 1 :(得分:3)

评论太长了。

我假设你有一个无损消息队列,一旦你得到写入数据的确认,队列就保证有记录。

基本上,您需要一个循环,其中包含可以回滚的事务或数据库中的状态。事务的伪代码是:

  • 开始交易
  • 插入数据库
  • 写入邮件队列
  • 当消息队列确认时,提交事务

就个人而言,我可能会以以下状态执行此操作:

  • 插入状态为“待定”(或类似内容)的数据库
  • 写入邮件队列
  • 当消息确认时,将状态更改为“已提交”(或类似内容)

如果从故障中恢复,您可能需要检查消息队列以查看是否有任何“待处理”记录实际写入队列。

答案 2 :(得分:1)

除了@Gordon Linoff所说的,假设持久消息传递(类似于MSMQ?),方法/处理程序将是事务性的,所以如果它全部成功,则消息将被写入队列并且数据到您的视图模型,如果失败,一切都将失败......

要缓解ID问题,您需要使用GUID而不是DB生成的密钥(如果您使用的是消息,则无论如何都需要删除参照完整性并将GUIDS作为密钥引入)。

还有一个建议,不要更新数据库,但是只插入/ upsert(挂起的行然后是完成的行)并让读者根据最新的行(例如)进行数据的投影

答案 3 :(得分:1)

恐怕答案(VoiceOfUnreason,乌迪·达汉)只会把问题笼罩在地毯下。地毯下的问题是:应该如何设计从数据库到队列的数据移动,以使消息仅发布一次(不使用XA)。如果解决了这个问题,那么您可以通过任何其他业务逻辑轻松扩展该概念。

CAP theorem清楚地告诉您限制。

XA交易不是100%的防弹解决方案,但在我看来,我认为是最好的选择。

答案 4 :(得分:0)

将消息作为事务的一部分是一个好主意,但是它有很多缺点,例如

如果您的

a。数据库/语言不支持事务

b。交易是耗时的操作

c。您无法在响应服务呼叫时等待队列响应。

d。如果您的数据库已经处于压力之下,那么编写消息将加剧更高工作负载的影响。

最佳做法是使用数据库流。大多数现代数据库都支持流(Dynamodbmongodborcale等)。您有运行数据库流的使用者,该数据库流从数据库流中读取数据并写入队列或使高速缓存无效,将其添加到搜索索引器等。一旦所有这些操作成功,就将流项目标记为已处理。

这种方法的优点

  1. 在区域故障的多区域部署中可以使用。 (您应该阅读区域数据流并充实所有区域数据存储。)

  2. 没有写更多记录或性能瓶颈的麻烦。

  3. 您可以将此模式用于其他数据源,例如缓存,排队,搜索。

缺点

  1. 您可能需要调用多个服务来构造适当的消息。

  2. 一个数据库流可能不足以构造适当的消息。

  3. 确保流的可靠性,例如redis stream is not reliable

注意,这种方法也不能保证语义一模一样。使用者逻辑应该是幂等的,并且应该能够处理重复的消息