假设我在事务范围中登记了Rebus和数据库连接(例如sql server连接)。在数据库连接上会执行一些数据库操作,Rebus会发布一些消息,事务范围不会升级到MSDTC(我检查过Windows上没有分布式事务,而且这种情况在不支持MSDTC的Linux上也适用)。Complete()在事务作用域上调用,它指示数据库连接和Rebus提交。现在,让我们假设数据库连接首先提交并成功,在Rebus提交(=发布消息)之前,机器崩溃。会发生什么?我能想到这些场景:
- 数据库操作已提交,但未发布任何消息(状态不正确)
- 数据库操作被回滚(由于MSDTC不会参与,由谁不确定,并且当机器重新启动时,我认为没有人会检查崩溃期间事务发生了什么),并且没有发布任何消息(正确的状态)
- 在机器重新启动(正确状态)后,数据库操作被提交,消息被发布(由谁发布?)
此外,我使用NServiceBus检查了相同的场景,当与MSMQ一起使用时,事务范围将升级到MSDTC,并且NServiceBus的创建者声称,无论计算机是否在事务范围的任何点崩溃,事务范围总是会有正确的结果-要么全部提交,要么全部回滚。
处理消息时,Rebus按以下顺序执行工作:
- 您的处理程序被执行(可能涉及数据库事务以提交您自己的工作)
- 发送传出邮件
- 传入消息将从队列中删除
由于世界上到处都是失败的,您的程序可能会在这些步骤之间(或在!)的任何时候失败。
如果故障发生在(1)之前或期间,则没有问题,因为您自己的工作(至少在本例中)是在可以原子回滚的事务中执行的。
如果在(1)之后和完全完成(3)之前出现故障,那么您将体验到Rebus的"至少一次"交付保证,这意味着在发生此类故障的情况下,消息将至少处理一次,这意味着您可能会处理两次,如果您不幸的话,可能会处理更多。
无法逃避这个事实,所以如果你关心这种情况,你需要使你的消息处理程序幂等。
幂等性可以通过多种方式实现:有时是由于操作本身是幂等的(例如,简单地打乱接收到的数据,将某些字段的值设置为消息中的值,等等),有时是依靠能够丢弃过时的数据(例如,如果您可以将数据的"上次更改"值与消息中的更新时间戳进行比较)。
但是,有时,如果您的系统在处理重新传递的消息时最终处于糟糕的状态,您需要仔细地对其进行编码,例如,将已处理消息的消息ID存储在一个对ID有唯一约束的表中。
棘手的部分是:真正的幂等性要求您模拟所有公开可见的行为,也就是在第二次处理消息时。这意味着在处理您的邮件时发送/发布的所有邮件也必须第二次发送和发布。
正如您可能想象的那样,实现真正的幂等性并不总是微不足道的。
(…)NServiceBus的创建者声称,无论机器是否在事务范围的任何点崩溃,事务范围总是会有正确的结果-要么全部提交,要么全部回滚(…)
对于分布式事务和两阶段提交,这不可能是真的,因为可能会出现第三种结果:所有事务都在准备阶段确认,然后其中一个在提交阶段失败(因为网络中断、磁盘已满或其他一些不可恢复的问题),那么事务协调器别无选择,只能挂起事务,需要手动干预才能让世界继续。
此外,我用NServiceBus检查了相同的场景,当与MSMQ一起使用时,事务范围将升级到MSDTC,NServiceBus的创建者声称事务范围总是有正确的结果-要么全部提交,要么全部回滚,无论计算机是否在事务范围的任何点崩溃。
@mookid8000提到,即使使用分布式事务,也没有100%的保证。这是因为两个将军的问题。但您可以说,使用分布式事务在可靠性方面胜过其他一切。不幸的是,它在SQL Server中为数据创建了大量开销和Serializable
锁。Oracle甚至不支持它。大多数DBA讨厌它是有原因的。我已经使用MSMQ和SQL Server构建了运行良好的系统,但这需要一些思考。
另一件事是,大多数资源不支持分布式事务。就像在中一样,云中的一切,RabbitMQ和许多其他技术。
@mookid8000提到的一个好的解决方案是将每个传入消息的标识符存储到数据库中,并验证该消息是否已经被处理。但它还不止于此。想象一下,发布一个标识符为1b068720-b558-4edf-9ebd-7142bc8cd3c0
的事件。然后,我们试图告诉队列它可以删除消息,但由于出现错误,我们未能删除。我们什么时候将消息标识符存储在数据库中并提交该事务?它成功与否?如果我们再次处理传入消息,会在数据库中找到标识符吗?可能,但该标识符是在我们发布事件之前还是之后提交的?每一步都可能失败!
问题是,该活动会再次发布吗?因为如果会,用什么标识符?可能是一个新的独特的,像00d13f2b-ce5b-4880-9a5b-2cb541015902
。这里的问题是,接收端点如何知道这是同一个逻辑消息,并且不应该处理它,因为我们已经处理了消息,但使用了另一个标识符?我们需要确保事件确实已发布,但如果我们再次发布,则它具有完全相同的标识符。否则,幂等性在另一边是非常困难的,如果不是不可能的话!
这就是Outbox模式的作用所在。
正如你所看到的,构建分布式系统并确保它们是故障安全的并不是那么容易。如果你有更多的问题,你可以随时联系。