【问题标题】:Machine crash when publishing a message with Rebus and a database connection enlisted in a transaction scope使用 Rebus 和事务范围中登记的数据库连接发布消息时机器崩溃
【发布时间】:2018-08-28 19:16:58
【问题描述】:

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

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

另外,我用 NServiceBus 检查了相同的场景,当与 MSMQ 一起使用时,事务范围升级到 MSDTC,并且 NServiceBus 的创建者声称事务范围的结果总是正确的——要么全部提交,要么全部回滚,无论机器是否在事务范围的任何点崩溃。

【问题讨论】:

  • > 和 NServiceBus 的创建者声称你能帮我一个忙并澄清你在哪里读到的吗?如果我们能更好地解释这一点,我们将不胜感激了解我们做得不够好的地方。
  • 1/2 关于“NServiceBus 的创建者声称......” - 这是我对 Udi Dahan 谈话的印象:vimeo.com/111998645(时间 12:42 - “DTC 为您解决所有这些问题” ,时间 14:39“DTC 给您的一次且仅一次的消息传递”)。他谈到了 NServiceBus 5 中的一个实现,它应该在不使用 DTC 的情况下与 DTC 一样可靠,并且应该能够承受崩溃并且在任何代码行崩溃时都可靠。这次演讲给我的印象是,您可以将 NServiceBus 与 MSMQ/sql 服务器和 DTC 一起使用,并相信“它可以正常工作”。是不是这样?
  • 2/2 是否存在带有 MSMQ/sql 服务器和 DTC 的 NServiceBus 不能“正常工作”的情况?例如机器在某个特殊点崩溃,导致事情无法按预期工作 - 消息错误地没有发布,或者发布了两次或多次?
  • 只是一个大概的数字,但 MSMS + SQL 的可靠性约为 99.99%。如果您每秒发送 5 条消息,每分钟 300 条,每小时 18k 条,每年 650 万条,那么每年 657 条消息可能永远不会到达。再一次,只是一个大概的数字。情况可能不太糟。但这是可能的,因为没有什么是 100% 故障安全的。
  • 那就是与 msdtc。当您不使用分布式事务时,丢失消息的数量可能会成倍增加。发件箱可以解决很多问题。

标签: .net nservicebus transactionscope rebus


【解决方案1】:

在处理消息时,Rebus 按以下顺序执行其工作:

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

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

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

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

这个事实无法回避,所以如果你关心这种情况,你需要让你的消息处理程序idempotent

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

但有时,如果您的系统因处理重新传递的消息而最终处于错误状态,您需要精心编写代码来摆脱它,例如通过将已处理消息的消息 ID 存储在对 ID 具有唯一约束的表中。

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

正如您可能想象的那样,实现真正的幂等性并不总是微不足道的。

(...) NServiceBus 的创建者声称事务范围的结果总是正确的——要么全部提交,要么全部回滚,无论机器是否在事务范围的任何点崩溃(.. .)

对于分布式事务和两阶段提交,这不可能是真的,因为存在第三种结果的可能性:所有事务在准备阶段确认,然后其中一个在提交阶段失败(因为网络中断、磁盘已满或其他无法恢复的问题)——那么事务协调器别无选择,只能让事务挂起,需要人工干预才能让世界继续下去。

【讨论】:

  • 我的场景不是处理传入的 Rebus 消息并可能处理两次或多次。我的场景是关于处理代表命令的 Web 请求,命令处理程序执行一些 DB 操作并通过 Rebus 发布事件消息(该命令已发生),所有这些都在事务范围内。我想知道机器是否恰好在成功提交数据库操作时崩溃,但 Rebus 尚未发布消息 - 消息是否可能根本不会发布?
  • 那么简短的回答:是的,这是可能的。您基本上可以选择首先提交哪个操作,然后总是存在第二个操作被中断的风险。
  • 我只想指出网络中断或磁盘已满并非不可恢复。当网络重新连接或清理磁盘空间时,这些将由 DTC 自动处理。数据在那里,它作为准备阶段的一部分被持久化。如果磁盘发生故障并且永远不会恢复(例如火灾),您可能会挂起事务。这是当今非常罕见的情况,因为大多数生产环境都是虚拟化的,并且使用的存储解决方案可以容忍单个硬件部件出现故障。
【解决方案2】:

另外,我用 NServiceBus 检查了相同的场景,当与 MSMQ 一起使用时,事务范围升级到 MSDTC,并且 NServiceBus 的创建者声称事务范围的结果总是正确的——要么全部提交,要么全部回滚,无论机器是否在事务范围的任何点崩溃。

正如@mookid8000 所提到的,即使使用分布式事务,也没有 100% 的保证。原因是2 generals problem。但是你可以说使用分布式事务在可靠性方面胜过其他一切。不幸的是,它会产生大量开销,并且Serializable 会锁定您在 SQL Server 中的数据。甲骨文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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-09
    相关资源
    最近更新 更多