c# - 涉及 EntityFramework/SQL Server 和 NServiceBus/MSMQ 的分布式事务中的非同时提交

标签 c# sql-server nservicebus msmq msdtc

有一个 .NET 4.7 WebAPI 应用程序使用 Entity Framework 与 SQL Server 一起工作,并通过 MSMQ 传输托管 NServiceBus 端点。

简化的工作流程可以通过 Controller 操作来描述:

[HttpPost]
public async Task<IHttpActionResult> SendDebugCommand()
{
    var sample = new Sample
                 {
                     State = SampleState.Initial,
                 };
    _dataContext.Set<Sample>().Add(sample);
    await _dataContext.SaveChangesAsync();

    sample.State = SampleState.Queueing;

    var options = new TransactionOptions
                  {
                      IsolationLevel = IsolationLevel.ReadCommitted,
                  };
    using (var scope = new TransactionScope(TransactionScopeOption.Required, options, TransactionScopeAsyncFlowOption.Enabled))
    {
        await _dataContext.SaveChangesAsync();    
        await _messageSession.Send(new DebugCommand {SampleId = sample.Id});
        scope.Complete();
    }

    _logger.OnCreated(sample);

    return Ok();
}

DebugCommand 处理程序,发送到同一个 NServiceBus 端点:

public async Task Handle(DebugCommand message, IMessageHandlerContext context)
{
    var sample = await _dataContext.Set<Sample>().FindAsync(message.SampleId);

    if (sample == null)
    {
        _logger.OnNotFound(message.SampleId);
        return;
    }

    if (sample.State != SampleState.Queueing)
    {
        _logger.OnUnexpectedState(sample, SampleState.Queueing);
        return;
    }

    // Some work being done

    sample.State = SampleState.Processed;
    await _dataContext.SaveChangesAsync();

    _logger.OnHandled(sample);
}

有时,消息处理程序从数据库中检索Sample,其状态仍然是Initial,而不是预期的Queueing。这意味着在 Controller Action 中发起的分布式事务还没有完全完成。日志文件中的时间戳也证实了这一点。

“有时”很少发生,在较重的负载和网络延迟下可能会受到影响。无法使用本地数据库重现问题,但使用远程数据库很容易。

我检查了 DTC 配置。我检查了肯定有升级到分布式事务。此外,如果未调用 scope.Complete(),则不会发生数据库更新和消息发送。

当事务作用域完成并释放后,直觉上我希望在执行单个进一步指令之前,DB 和 MSMQ 都得到解决。

我找不到问题的明确答案:

  • 这是 DTC 的工作方式吗?事务双方都进行提交,但未将完成情况报告给协调器,这是否正常?
  • 如果是,是否意味着我应该通过改变程序逻辑来克服此类事件?
  • 我是否以某种方式滥用了交易?什么是正确的方法?

最佳答案

除了 Evk 在 Distributed transaction with MSMQ and SQL Server but sometimes getting dirty reads 中提到的评论这也是 particular documentation page about transactions 的摘录:

A distributed transaction between the queueing system and the persistent storage guarantees atomic commits but guarantees only eventual consistency.

两个附加说明:

  • NServiceBus 默认使用 IsolationLevel.ReadCommitted 作为用于消费消息的事务。 This can be configured尽管我不确定将它设置为在消费者身上序列化是否真的能解决这里的问题。
  • 一般来说,不建议在服务之间使用共享数据库,因为这会大大增加耦合并为您在这里遇到的问题打开大门。尝试将相关数据作为消息的一部分传递,并将数据库作为一项服务的内部存储。特别是在使用 Web 服务器时,一种常见的模式是将所有相关数据添加到消息中并在向用户确认成功时触发它(因为消息不会丢失),而接收端点可以将数据存储到它的数据库中,如果必要的。要提供更具体的建议,这需要对您的领域和用例有更多了解。我可以推荐 particular discussion community像这样讨论设计/架构问题。

关于c# - 涉及 EntityFramework/SQL Server 和 NServiceBus/MSMQ 的分布式事务中的非同时提交,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47034005/

相关文章:

sql-server - 使用用户定义的表类型变量参数执行远程过程

c# - 实习生培训 - 最佳方法?

c# - NServicebus 添加到旧版 Windows 服务

sql-server - 在 C# 中从数据库获取字符串值

c# - 如何防止 NServiceBus 回滚事务或事务的一部分?

azure - NServiceBus 与 Azure 服务总线、全双工和多个发送器

c# - 将通用对象数组转换为两种类型

c# - 如何使用 linq 将 IEnumerable 转换为 Collection

c# - 不寻常的异常行为?

c# - 正则表达式 : trying to include unlimited whitespaces for any words in ASP. NET C#