12

在将 NHibernate 与分布式事务一起使用时,我们遇到了问题。

考虑以下代码段:

//
// There is already an ambient distributed transaction
//
using(var scope = new TransactionScope()) {
    using(var session = _sessionFactory.OpenSession())
    using(session.BeginTransaction()) {
        using(var cmd = new SqlCommand(_simpleUpdateQuery, (SqlConnection)session.Connection)) {
            cmd.ExecuteNonQuery();
        }

        session.Save(new SomeEntity());
        session.Transaction.Commit();
    }
    scope.Complete();
}

有时,当服务器处于极端负载下时,我们会看到以下内容:

  1. 使用cmd.ExecuteNonQuery执行的查询被选为死锁牺牲品(我们可以在 SQL Profiler 中看到),但没有引发异常。
  2. session.Save失败并显示错误消息“该操作对事务状态无效。”
  3. 此后每次执行此代码时,session.BeginTransaction都会失败。前几次,内部异常会有所不同(有时是应该在步骤 1 中引发的死锁异常)。最终它稳定到“服务器未能恢复事务。描述:3800000177”。“不允许启动新请求,因为它应该带有有效的事务描述符。”

如果不理会,应用程序最终会(在几秒钟或几分钟后)从这种情况中恢复。

为什么第一步没有报死锁异常?如果我们不能解决这个问题,那么我们如何防止我们的应用程序暂时变得不可用呢?

该问题已在以下环境中重现

  • Windows 7 x64 和 Windows Server 2003 x86
  • SQL Server 2005 和 2008
  • .NET 4.0 和 3.5
  • NHibernate 3.2、3.1 和 2.1.2

我创建了一个测试夹具,它有时会为我们重现该问题。可在此处获得:http ://wikiupload.com/EWJIGAECG9SQDMZ

4

4 回答 4

6

我们终于把这个范围缩小到一个原因。

打开会话时,如果存在环境分布式事务,NHibernate 会将事件处理程序附加到 Transaction.TransactionCompleted,它会在分布式事务完成时关闭会话。这似乎受到竞争条件的影响,其中连接可能会在死锁错误传播之前关闭并返回到池中,从而使连接处于不可用状态。

以下代码偶尔会为我们重现错误,即使服务器上没有任何负载。如果服务器上有极端负载,它会变得更加一致。

using(var scope = new TransactionScope()) {
    //
    // Force promotion to distributed transaction
    //
    TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);

    var connection = new SqlConnection(_connectionString);
    connection.Open();

    //
    // Close the connection once the distributed transaction is
    // completed.
    //
    Transaction.Current.TransactionCompleted += 
        (sender, e) => connection.Close();

    using(connection.BeginTransaction())
        //
        // Deadlocks but sometimes does not raise exception
        //
        ForceDeadlockOnConnection(connection);

    scope.Complete();
}

//
// Subsequent attempts to open a connection with the same
// connection string will fail
//

我们还没有确定解决方案,但以下事情将消除问题(同时可能产生其他后果):

  • 关闭连接池
  • 使用 NHibernate 的AdoNetTransactionFactory而不是AdoNetWithDistributedTransactionFactory
  • 添加在“服务器无法恢复事务”错误发生时调用SqlConnection.ClearPool()的错误处理

根据 Microsoft (https://connect.microsoft.com/VisualStudio/feedback/details/722659/),SqlConnection 类不是线程安全的,这包括在单独的线程上关闭连接。基于此响应,我们提交了 NHibernate 的错误报告 (http://nhibernate.jira.com/browse/NH-3023)。

于 2012-01-17T20:47:26.947 回答
0

不是一个明确的答案,但我怀疑您在会话管理方面存在一些问题,并且您在对处理程序的多次调用中使用相同的会话。我认为实际上不是处于不良状态的连接,而是休眠会话。这似乎与您没有看到关闭连接池的问题无关,所以我可能不在基地,但我仍然怀疑它与重用会话有关。

我建议的第一件事是您尝试通过记录会话的哈希码和 session.GetSessionImplementation() 的哈希码来确认这一点(我对使用城堡 nhibernate 设施的理解是您将看到相同的会话实例,甚至尽管它实际上是一个不同的会话,并且会话实现实际上会显示出差异)。看看您是否看到在处理不同消息时使用了相同的哈希码。

如果是会话管理问题,请尝试使用 nservicebus 模块来管理处理程序的会话。这是安德烈亚斯关于这样做的帖子。我不认为他在 2.5 版本中对在主干上内置的方法进行了编辑,因此您可能希望继续进行此操作。(我可能错了。)

http://andreasohlund.net/2010/02/03/nhibernate-session-management-in-nservicebus/

于 2011-12-20T21:32:09.010 回答
0

这并不能完全解决您的问题,但您可以让您的 IPreInsertEventListener 只发送一条 NSB 消息,然后让消息的接收者调用存储过程。在过去使用 NHibernate 和 NSB 时,我已经使用有问题的前后事件侦听器来完成此操作。

另一个想法是让您的事件前监听器创建自己的连接对象,并用一个漂亮的 using 语句包装,然后它就不会触及 NHibernate 的连接。如果它死锁,那么只需抛出并确保您已经处理了范围内的任何对象。

于 2012-01-02T05:02:07.033 回答
0

这是一个NHibernate 问题。NHibernate 不在同一个线程上打开和关闭连接,这不受ADO.NET支持。您可以通过自己打开和关闭连接来解决它。NHibernate 不会关闭连接,除非它也打开了它。

解决方法

var connection = ((SessionFactoryImpl)_sessionFactory).ConnectionProvider.GetConnection();
using(var session = _sessionFactory.OpenSession(connection))
{
   //do database stuff
}
connection.Close();
于 2013-11-13T14:07:12.960 回答