使用Rebus和事务作用域中列出的数据库连接发布消息时,机器崩溃

时间:2018-08-28 19:16:58

标签: .net nservicebus transactionscope rebus

假设我在事务作用域中拥有Rebus和数据库连接(例如sql server连接)。数据库连接上将执行一些数据库操作,并且Rebus将发布一些消息,并且事务范围不会升级为MSDTC(我检查了Windows上是否没有分布式事务,并且这种情况也适用于Linux,MSDTC不支持)。在事务作用域上调用Complete(),它指示数据库连接和Rebus都提交。现在,我们假设数据库连接首先提交并成功,并且在Rebus可以提交(=发布消息)之前,计算机将崩溃。会发生什么?我可以想到以下情形:

  1. 已落实数据库操作,但未发布任何消息(状态不正确)。
  2. 数据库操作将回滚(不确定由谁作为MSDTC参与,并且当计算机重新启动时,我认为没有人会检查崩溃期间事务发生了什么),并且没有消息发布(正确的状态)。
  3. 在机器重新启动(正确状态)后,将执行数据库操作,并发布消息(由谁发布)。

此外,我在NServiceBus中检查了相同的情况,当与MSMQ一起使用时,事务范围被升级为MSDTC,并且NServiceBus的创建者声称,事务范围总是有正确的结果-所有提交或全部无论机器是否在事务作用域的任何位置崩溃,都可以回滚。

2 个答案:

答案 0 :(得分:1)

处理邮件时,Rebus按以下顺序执行其工作:

  1. 您的处理程序已执行(可能涉及数据库事务来提交您自己的工作)
  2. 已发送邮件
  3. 传入消息已从队列中删除

由于世界充满了失败,因此您的程序可能会在这些步骤之间(或期间!)的任何时候失败。

如果在(1)之前或期间发生故障,则没有问题,因为您的工作(至少在这种情况下)是在可以原子回滚的事务中执行的。

如果在(1)之后到完全完成(3)之前发生故障,那么您将体验到Rebus的“至少一次”交付保证,这意味着-在发生此类故障时-消息将得到处理至少一次,这意味着它可以处理两次,如果不幸的话,甚至可能会处理更多。

没有逃避这个事实,因此,如果您担心这种情况,则需要使消息处理程序idempotent

幂等可以通过多种方式实现:有时,由于操作本身是幂等的(例如,仅对接收的数据进行升序处理,将某些字段的值设置为消息中的值,等等),有时依靠能够丢弃过时的数据(例如,如果您可以将数据的“上次更改”值与消息中的更新时间戳进行比较)。

但是有时候,如果您的系统由于处理重新传递的消息而最终陷入不良状态,则您需要精心编写出避免错误的代码,例如通过将已处理消息的消息ID存储在具有唯一ID约束的表中。

棘手的部分是:真正的幂等性要求您模仿所有公开可见的行为,以及第二次处理消息时。这意味着在处理您的消息时发送/发布的所有消息也必须第二次发送和发布。

您可能会想到,实现真正的幂等性并不总是那么简单。

  

(...)NServiceBus的创建者声称,交易范围总会有正确的结果-无论机器是否在交易范围的任何位置崩溃,都将全部提交或全部回退。 )

对于分布式事务和两阶段提交,这是不正确的,因为可能会出现第三种结果:所有事务在准备阶段都进行确认,然后其中一个在提交阶段失败(由于网络)停机,磁盘已满或其他一些不可恢复的问题),那么事务协调员别无选择,只能暂停事务,需要人工干预才能使世界继续运转。

答案 1 :(得分:1)

  

此外,我在NServiceBus中检查了相同的情况,当与MSMQ一起使用时,事务范围被升级为MSDTC,并且NServiceBus的创建者声称,事务范围总是有正确的结果-所有提交或全部无论机器是否在事务作用域的任何位置崩溃,都可以回滚。

正如@ mookid8000所提到的,即使使用分布式事务,也没有100%的保证。原因是2 generals problem。但是您可以说,使用分布式事务在可靠性方面胜过其他一切。不幸的是,这会产生大量开销,并且Serializable会锁定SQL Server中的数据。 Oracle doesn't even support it。大多数DBA都不喜欢这样做,这是有原因的。我已经使用MSMQ和SQL Server构建了运行良好的系统,但这需要一些思考。

另一件事是,大多数资源不支持分布式事务。就像云中的一切一样,RabbitMQ和许多其他技术也是如此。

@ mookid8000提到的一个好的解决方案是将每条传入消息的标识符存储到数据库中,并验证该消息是否已被处理。但这并不止于此。想象一个事件以标识符1b068720-b558-4edf-9ebd-7142bc8cd3c0发布。然后,我们尝试告诉队列它可以删除消息,但是由于错误而未能这样做。我们什么时候将消息标识符存储在数据库中并提交了该事务?它成功与否?如果我们再次处理传入消息,是否会在数据库中找到标识符?可能是,但是该标识符是在发布事件之前还是之后提交的?每一步都会失败!

问题是,该事件会再次发布吗?因为如果会,用什么标识符?可能是一个新的唯一变量,例如00d13f2b-ce5b-4880-9a5b-2cb541015902。这里的问题是,接收端点如何知道这是相同的逻辑消息,并且由于我们已经处理了该消息但使用了另一个标识符,因此不应对其进行处理?我们需要尝试确保该事件确实已发布,但是如果再次发布该事件,则它具有完全相同的标识符。否则,另一端的幂等性很难,甚至不可能!

这就是Outbox Pattern出现的地方。

如您所见,构建分布式系统并确保它们具有故障安全性并不是那么容易。如果您还有其他问题,可以随时reach out