为啥我的 MSDTC 事务没有在我的 localhost 环境中正确回滚?

Posted

技术标签:

【中文标题】为啥我的 MSDTC 事务没有在我的 localhost 环境中正确回滚?【英文标题】:Why is my MSDTC transaction not correctly rolling back on my localhost environment?为什么我的 MSDTC 事务没有在我的 localhost 环境中正确回滚? 【发布时间】:2017-03-03 16:55:56 【问题描述】:

我目前正在尝试针对 SQL Server 2016 编写一个简单的 C# 应用程序,以使用 Microsoft 分布式事务 (MSDTC)。在尝试通过网络在服务器上对其进行测试之前,我正在尝试让它在我的本地计算机(Windows 10 Pro)上运行。

.NET 解决方案由一个控制台应用程序项目和三个独立的 webapi 项目组成。控制台应用程序调用每个 webapi 项目,然后每个 webapi 项目将一个简单的记录写入数据库。这似乎工作正常。然后我想做的是让第三个 webapi 项目生成一个异常,并将所有以前的记录作为分布式事务的一部分回滚。但是,我的问题是前两个项目正在将他们的记录提交到数据库而不是回滚。

我设置它的方式(我相信它是正确的)是让每个项目使用System.Transactions 来登记使用 DTC。

控制台应用程序充当根事务管理器并通过执行类似于以下操作来启动事务:

using (TransactionScope scope = new TransactionScope())

    var orderId = Guid.NewGuid();
    ProcessSales(orderId);
    ProcessBilling(orderId);
    ProcessShipping(orderId);

    scope.Complete();

上述每个Process...() 方法都使用HttpClient 调用其对应的webapi 项目。

然后,每个 webapi 项目都应使用另一个事务加入根事务:

using (TransactionScope scope = new TransactionScope())

    string connectionString = @"Server=MyServerNameHere;Database=MyDatabaseNameHere;Trusted_Connection=True;";

    using (SqlConnection connection = new SqlConnection(connectionString))
    
        string sql = "INSERT INTO dbo.Shipping (ShippingId, OrderId, Created) VALUES (@ShippingId, @OrderId, GetDate())";

        using (SqlCommand command = new SqlCommand(sql, connection))
        
            ...
            command.ExecuteNonQuery();
        
    

    scope.Complete();

第三个 webapi 项目在初始化 TransactionScope 之后有 throw new Exception();

要在我的机器上设置 MSDTC,我已按照here 中描述的文章进行操作,并执行了以下操作:

    检查“分布式事务协调器”服务是否在管理工具 -> 服务中运行。

    已设置 Windows 防火墙以允许分布式事务:

    控制面板 -> Windows 防火墙 -> 允许应用程序或功能通过 Windows 防火墙 -> 将“分发事务协调器”添加到列表中。

    在组件服务中启用网络事务:

    组件服务 -> 扩展计算机 -> 展开 MyComputer -> 展开分布式事务协调器 -> 右键单击​​“本地 DTC”并选择属性 -> 选择安全选项卡

    这些是当前设置:

我也一直在查看here 中描述的一些故障排除信息。本文中的步骤之一描述了下载 DTCTool 并运行 RMClient.exe。我已经这样做了,它生成了以下日志:

错误(RpcStatus=1753):在 RPCuser.cpp(行:116)RPC 服务器过程 登录失败 (1753)

调试提示::此错误意味着客户端能够查询 指定目标机器上端口 135 的端点映射器,但要么 无法联系 WinRM 服务器。检查:

    防火墙阻止更高端口 - 获取客户端和服务器之间的 netmon 跟踪并分析此行为的流量 如果服务器上启用了 Windows 防火墙,请检查 WinRM.exe 是否在例外列表中 在集群环境中检查集群名称是否解析到运行 MSDTC/WinRM 的节点 在集群环境中,在客户端和 MSDTC 集群名称之间进行 DTCPing 测试,看看是否有效 客户端无法登录服务器

我不确定此日志是否相关,但我现在有点卡住了。我暂时关闭了防火墙,但它仍然没有工作,所以第 1 点和第 2 点应该不是问题。而且我没有使用集群环境,所以第 3 点和第 4 点应该不是问题。

我不确定是否要在我的应用程序中正确设置 MSDTC。我只是把我可以从互联网上找到的关于它的点点滴滴放在一起。对此的任何帮助将不胜感激。

谢谢

【问题讨论】:

您丢失了您的服务调用实际传输交易信息的任何信息。您是否在服务上设置了任何属性来自动执行此操作?仅使用TransactionScope 并不能保证分布式事务,您必须分发它。 我相信每次在 using 语句中创建 TransactionScope 都会创建新的范围。因此,调用 3 个 api 的事务范围与在单个 api 中创建的事务范围不同。您需要在同一事务范围内运行所有三个 webapi,然后在其中任何一个失败时将回滚。你可能看过这个样本code.msdn.microsoft.com/Distributed-Transactions-c7e0a8c2 @nvoigt - 感谢您提供的信息。我错误地假设当前事务将由底层 MSDTC 传递,但现在我正在考虑它,这没有任何意义,尤其是当您考虑多用户环境中的多个事务时。 @Pankaj Kapare - 再次感谢您提供的信息。并感谢您提供的链接。这绝对是非常有帮助的。 【参考方案1】:

用户Pankaj Kapare 提供的link 对解决此问题非常有帮助。基本上,我实际上并没有将控制台应用程序(根事务管理器)中初始化的事务传递给任何 Web api 应用程序(这应该很明显)。因此,每个 web api 应用程序都在初始化自己的事务,而不是在现有事务中登记。

总结一下链接,在控制台应用程序中初始化的主事务可以使用 TransactionInterop 作为令牌检索,并作为请求标头或 cookie 的一部分传递给 webapi:

if (Transaction.Current != null) 
 
    var token = TransactionInterop.GetTransmitterPropagationToken(Transaction.Current); 
    request.Headers.Add("TransactionToken", Convert.ToBase64String(token));                 

在 web api 服务器应用程序中,可以检索事务令牌并用于注册主事务。文章建议使用效果很好的动作过滤器:

public class EnlistToDistributedTransactionActionFilter : ActionFilterAttribute 
 
    private const string TransactionId = "TransactionToken"; 

    /// <summary> 
    /// Retrieve a transaction propagation token, create a transaction scope and promote  
    /// the current transaction to a distributed transaction. 
    /// </summary> 
    /// <param name="actionContext">The action context.</param> 
    public override void OnActionExecuting(HttpActionContext actionContext) 
     
        if (actionContext.Request.Headers.Contains(TransactionId)) 
         
            var values = actionContext.Request.Headers.GetValues(TransactionId); 
            if (values != null && values.Any()) 
             
                byte[] transactionToken = Convert.FromBase64String(values.FirstOrDefault()); 
                var transaction = TransactionInterop.GetTransactionFromTransmitterPropagationToken(transactionToken); 

                var transactionScope = new TransactionScope(transaction); 

                actionContext.Request.Properties.Add(TransactionId, transactionScope); 
             
         
     

    /// <summary> 
    /// Rollback or commit transaction. 
    /// </summary> 
    /// <param name="actionExecutedContext">The action executed context.</param> 
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) 
     
        if (actionExecutedContext.Request.Properties.Keys.Contains(TransactionId)) 
         
            var transactionScope = actionExecutedContext.Request.Properties[TransactionId] as TransactionScope; 

            if (transactionScope != null) 
             
                if (actionExecutedContext.Exception != null) 
                 
                    Transaction.Current.Rollback(); 
                 
                else 
                 
                    transactionScope.Complete(); 
                 

                transactionScope.Dispose(); 
                actionExecutedContext.Request.Properties[TransactionId] = null; 
             
         
     

【讨论】:

以上是关于为啥我的 MSDTC 事务没有在我的 localhost 环境中正确回滚?的主要内容,如果未能解决你的问题,请参考以下文章

分布式事务如何工作(例如 MSDTC)?

已禁用对分布式事务管理器(MSDTC)的网络访问。请使用组件服务管理工具启用 DTC 以便在 MSDTC 安全配置中进行网络访问。

SqlServer & Windows 可更新订阅立即更新启用分布式事务协调器(MSDTC)

Azure 无法将事务编组为弹性事务的传播令牌(适用于 MSDTC)

怎么修复MSDTC服务

已禁用对分布式事务管理器(MSDTC)的网络访问。请使用组件服务管理工具启用 DTC 以便在 MSDTC 安全配置中进行网络访问。