【问题标题】:Why is NServiceBus Bus.Publish() not transactional?为什么 NServiceBus Bus.Publish() 不是事务性的?
【发布时间】:2023-03-13 19:12:02
【问题描述】:

设置: 我有几个订阅者通过 MSMQ 在同一台机器上订阅由发布者生成的事件。订阅者使用两个不同的端点名称,并在其各自的进程中运行。 (这是 NSB 4.6.3)

场景: 现在,如果我对其中一个订阅者做了一些“坏事”(比如删除 MSMQ 中接收消息的适当权限,或者直接删除 MSMQ 中的队列......),然后调用 Bus.Publish(),我仍然会有一个事件成功发布到“好”订阅者(如果订阅存储中订阅者列表中好的订阅者在坏订阅者之前),或者没有成功(如果坏订阅者在好的订阅者之前)。

结论: 这里的结果是 Bus.Publish() 似乎不是事务性的,因为要使向订阅者的发布全部成功或全部失败。根据列表中订阅者的顺序,最终结果可能会有所不同。

问题: 这种行为是设计使然吗? 这背后的想法是什么? 如果我想让这个调用具有事务性,推荐的方法是什么? (一个选项似乎在我的代码中将 Bus.Publish() 包含在 TransactionScope 中......)

【问题讨论】:

    标签: nservicebus


    【解决方案1】:

    发布是事务性的,或者至少在存在环境事务的情况下是事务性的。假设您没有采取步骤禁用事务,当您进入 Handle 方法时,所有消息处理程序都会运行环境事务。 (检查Transaction.Current.TransactionInformation 以了解第一手资料。)但是,如果您使用IWantToRunWhenBusStartsAndStops 进行操作,则不会有环境事务,所以是的,您需要使用自己的TransactionScope 进行包装。

    如何处理传递(特定于 MSMQ 传输)取决于目标是本地队列还是远程队列。

    远程队列

    对于远程队列,发布者根本不直接处理传递。可以这么说,它只是将两条消息放在“发件箱”中。 MSMQ 使用存储转发来确保这些消息最终被传递到它们的预期目的地,无论是在同一台机器上还是在远程机器上。在这些情况下,您可能会查看您的传出队列,并看到由于您对它们的目的地所做的任何事情而无法传递的消息卡在那里。

    存储转发提供的安全性意味着一个错误的订阅者不能取消发布者,因此减少了整体耦合。这是一件好事!但这也意味着在部署 NServiceBus 系统时,监控传出队列是 DevOps 故事中非常重要的一部分。

    本地队列

    对于本地队列,MSMQ 在技术上可能仍会在其自己的管道中使用外发队列的概念——我不确定,这并不重要。但是,MSMQ 能够(并且确实)执行的另一个步骤是在您尝试发送到本地队列之前检查本地队列的存在,如果它不存在或它有问题,则会抛出异常。这确实会影响发布者。

    所以,是的,如果您从非事务状态(例如 IWantToRunWhenBusStartsAndStops 内部)发布消息,并且停机队列恰好在订阅存储列表中的第 2 位,您可以观察到消息到达订阅者 A但不是在订阅者 B。如果它在禁用事务的消息处理程序中,由于消息重试逻辑,您可以看到多个副本到达订阅者 A!

    结果

    IWantToRunWhenBusStartsAndStops 非常适合快速演示和证明事情,但尽量少在其中加入真正的逻辑,而是选择环境事务适用的消息处理程序的安全性。还要记住,里面的异常可能会导致您的主机进程崩溃。当然不要在没有用您自己的交易包装的情况下在其中发布。

    【讨论】:

    • 感谢您的回复,大卫。在我的观察中,当消息被发送到本地队列时,MSMQ 会走捷径,因此不会将消息放在传出队列中。当它无法将消息直接放入本地目标队列时,它会引发一个错误,该错误会传播到 NSB 并导致发布部分成功/失败。我在 MSMQ 中是否遗漏了一些设置来强制它总是首先将消息放在传出队列中?
    • 完全忘记了 MSMQ 会这样做。更新了我的答案。谢谢!
    • 感谢大卫的精彩写作!
    • 如果您使用 SQLTransport,行为是否与此不同?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-17
    • 2020-06-21
    相关资源
    最近更新 更多