NHibernate和WCF:性能(会话重用)与并发(并发请求)

Fed*_*man 5 nhibernate concurrency performance wcf sessionfactory

我们正在与一组基本上无法改变的相同结构的遗留数据库进行不同的集成.为此,我们添加了一个辅助数据库,用于保存元信息,路由规则和临时保存旧数据库数据等内容.

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

我一直在做一个处理这两个问题的平衡行为,但并没有真正消除并发问题.

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

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode=ConcurrencyMode.Single]
public class LegacyGateService : ILegacyGateService
Run Code Online (Sandbox Code Playgroud)

早些时候,在从互联网上获得一些"灵感"(阅读:复制/粘贴)之后,我最终分别为辅助数据库和遗留数据库添加了一组名为XxxNHibernateUtil的类.这些类控制NHibernate会话,并从预先初始化的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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  <property name="current_session_context_class">call</property>
Run Code Online (Sandbox Code Playgroud)

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

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

我认为我需要以不同的方式生成和处理Sessions,以便以有序的方式处理相同数据库的不同事务,而不会相互干扰.但是,我对如何使这项工作缺乏了解.任何帮助是极大的赞赏!


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

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

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

对于另一种服务方法,辅助数据库不会出现这些问题,但过了一段时间后,我开始使用遗留数据库的事务获取ZombiedTransaction异常(死锁).还是......是什么给出的?

Pau*_*ner 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
            ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

暂且不说:理想情况下,您将依赖注入ISessionFactory服务.