5

我们正在对大量结构相同的遗留数据库进行不同的集成,这些数据库基本上无法更改。为此,我们添加了一个辅助数据库,用于保存元信息、路由规则以及临时保存遗留数据库的数据。

我们主要使用 NHibernate 来连接数据库。一个应用程序是 WCF 服务,它需要将传入数据插入到非常宽(数十列)的嵌套表中。显然,性能是一个问题,所以我一直在寻找尽可能经济的 NHibernate 交易。同时,并发似乎是一个问题。在生产中,我们开始遇到一些僵化的事务错误(死锁)。

我一直在平衡处理这两个问题,但并没有真正消除并发问题。

服务行为设置为一次处理一个请求,如下所示:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode=ConcurrencyMode.Single]
public class LegacyGateService : ILegacyGateService

早些时候,在互联网的一些“灵感”(阅读:复制/粘贴)之后,我最终添加了一组名为 XxxNHibernateUtil 的类,分别用于辅助数据库和遗留数据库。这些类控制 NHibernate Sessions 并从预初始化的 SessionFactories 生成或重用 Sessions。

对于辅助数据库,它看起来像这样:

public static class LegacyGateNHibernateUtil
{
    private static readonly ISessionFactory sessionFactory = BuildSessionFactory();

    private static ISessionFactory BuildSessionFactory()
    {
        try
        {
            Configuration Cfg = new Configuration();
            Cfg.Configure();
            Cfg.AddAssembly("LegacyGate.Persistence");
            return Cfg.BuildSessionFactory();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    public static ISessionFactory GetSessionFactory()
    {
        return sessionFactory;
    }

    public static ISession GetCurrentSession()
    {
        if (!CurrentSessionContext.HasBind(GetSessionFactory()))
            CurrentSessionContext.Bind(GetSessionFactory().OpenSession());

        return GetSessionFactory().GetCurrentSession();
    }

    public static void DisposeCurrentSession()
    {
        ISession currentSession = CurrentSessionContext.Unbind(GetSessionFactory());

        if (currentSession != null)
        {
            if (currentSession.IsOpen)
                currentSession.Close();
            currentSession.Dispose();
        }
    }
}

每当事务需要会话时,都会在服务请求调用期间查找并重新使用当前会话。或者至少:这就是应该发生的事情。

编辑:会话上下文当然是在 hibernate.cfg.xml 中设置的,如下所示:

  <property name="current_session_context_class">call</property>

对于遗留数据库,NHibernateUtil 适用于处理不同的可能数据库。为此,每个连接都建立了自己的 SessionFactory,必须在 Dictionary 集合中查找。否则,原理相同。

使用 WCFStorm 进行测试,一次发送一个请求时这似乎工作正常,但是一旦我开始负载测试,即使只有一个代理和长间隔,我也会得到大量不同类型的异常,都指向同时请求和相互破坏的交易。我曾尝试调整 IsolationLevel,但现在可用。

我认为我需要以不同的方式生成和处理 Session,以便以有序的方式处理对同一数据库的不同事务,并且不会相互干扰。但是,我对如何完成这项工作缺乏一些见解。任何帮助是极大的赞赏!


编辑对于一种服务方法,当使用多个代理进行测试时,前十几个调用工作正常,然后开始出现以下仅与辅助数据库相关的异常字符串:

  1. “将 IDataReader 转换为 NDataReader 时出现问题”/“读取器关闭时调用元数据的尝试无效。”
  2. “非法访问加载集合”
  3. “开始失败并出现 SQL 异常”/“超时已过期。在操作完成之前超时时间已过或服务器没有响应。”
  4. “无法执行查询”/“ExecuteReader 需要一个打开且可用的连接。连接的当前状态为关闭。”
  5. “无法初始化集合:”/“超时。在操作完成之前超时时间已过或服务器没有响应。”
  6. “交易未成功启动”
  7. “事务要么与当前连接无关,要么已经完成。”
  8. “无法初始化集合:”/“读取器关闭时调用读取无效。”

至少,异常 1 表明同一个会话被多个线程访问(可能是调用)。其他的也表示当前会话被其他进程中断。但是,当我试图隔离呼叫并让它们排队时,这怎么可能呢?

对于另一种服务方法,辅助数据库不会出现这些问题,但一段时间后,我开始在遗留数据库的事务中遇到 ZombiedTransaction 异常(死锁)。仍然......什么给了?

4

1 回答 1

12

简单的答案:您不会重复使用 NHibernate 会话。

它们不是重量级对象,它们被设计为按照工作单元模式创建、操作和处置。尝试在多个请求中“共享”这些内容违背了它们的预期用途。

从根本上说,正确同步会话访问的成本几乎肯定会抵消您通过回收它们以避免重新初始化它们而获得的任何好处。让我们还考虑一下,这些成本只是您将要执行的 SQL 池中的一滴水。

请记住,NHibernate 的 Session Factory 是重量级对象。它是线程安全的,因此您可以而且应该在所有开箱即用的请求中共享一个实例。

您的代码在概念上应如下所示:

public class Service : IService
{
    static Service()
    {
        Configuration Cfg = new Configuration();
        Cfg.Configure();
        Cfg.AddAssembly("LegacyGate.Persistence");
        Service.SessionFactory = Cfg.BuildSessionFactory();
    }

    protected static ISessionFactory SessionFactory { get; private set; }

    public void ServiceMethod(...)
    {
        using(var session = Service.SessionFactory.CreateSession())
        {
            // Do database stuff
            ...
        }
    }
}

顺便说一句:理想情况下,您会将依赖注入ISessionFactory到服务中。

于 2012-01-05T13:54:26.007 回答