NHibernate& WCF:性能(会话重用)与并发(并发请求) [英] NHibernate & WCF: Performance (session reuse) vs. concurrency (simultaneous requests)

查看:344
本文介绍了NHibernate& WCF:性能(会话重用)与并发(并发请求)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们正在开发不同的集成到一组相同结构化的遗留数据库,基本上不能改变。为此,我们添加了一个辅助数据库,用于保存元信息,路由规则和临时保留旧数据库的数据。



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



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



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

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

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



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

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

private静态ISessionFactory BuildSessionFactory()
{
try
{
配置Cfg =新配置();
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();
}
}
}

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



EDIT:会话上下文当然是在hibernate.cfg.xml中设置的:

 < property name =current_session_context_class> call< / property> 

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



使用WCFStorm进行测试,在每次发送一个请求时似乎工作正常,但是一旦开始负载测试,即使只有一个代理和长间隔,我得到了大量的不同种类的异常,所有指向同时请求和事务破坏对方。我试过调整IsolationLevel,但现在可用。



我想我需要以不同的方式生成和处理Sessions,以便对同一数据库的不同事务以有序的方式处理,并且不会相互干扰。然而,我缺乏一些洞察如何使这项工作。任何帮助,非常感谢!






EDIT 对于一种服务方法,当使用多个代理进行测试时,或者所以调用工作正常,然后下面的字符串的异常开始出现只有属于辅助数据库:


  1. 转换时出现问题一个IDataReader到NDataReader/
    读取器关闭时尝试调用MetaData无效。

  2. 非法访问加载集合

  3. Begin failed with SQL exception/超时已过期。操作完成之前超时时间或服务器未响应。

  4. 无法执行查询/ExecuteReader需要开放且可用的连接。连接的当前状态已关闭。

  5. 无法初始化

  6. 交易未成功启动

  7. 事务未与当前连接关联或已完成。

  8. 无法初始化集合:/读取器关闭时尝试调用读取无效。

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



对于另一种服务方法,这些问题不会与辅助数据库一起出现,但在一段时间后,我开始获得ZombiedTransaction异常(死锁)与传统数据库的事务。还有...什么给了?

解决方案

简单的回答:不要重复使用NHibernate会话

它们不是重量级对象,它们被设计为根据工作单元模式创建,处理和处理。 尝试在多个请求之间分享这些内容违反了其预期用途。



从根本上说,肯定否定你通过回收他们获得的任何好处,以避免重新初始化它们。让我们还考虑这些成本是你将执行的SQL的池塘。



请记住,NHibernate的Session Factory是重量级的对象。



您的代码应该在概念上看起来像这样:

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

protected static ISessionFactory SessionFactory {get;私人集}

public void ServiceMethod(...)
{
using(var session = Service.SessionFactory.CreateSession())
{
//做数据库资料
...
}
}
}


$ b b

注意:理想情况下,您会将 ISessionFactory 依赖注入到服务中。


We are working on different integrations to a swarm of identically structured legacy databases that basically cannot be altered. For this, we added an auxiliary database for holding things like meta-information, routing rules and for temporarily holding data for the legacy databases.

We are mainly using NHibernate to connect to the databases. One application is a WCF Service that needs to inserts incoming data into nested tables that are really wide (dozens of columns). Obviously, performance is an issue, so I have been looking to be as economic as possible with the NHibernate transactions. At the same time, concurrency appeared to be an issue. In production, we were starting to get some zombied transaction errors (deadlocks).

I have been doing a balancing act to deal with these two issues but haven't really eliminated the concurrency problems.

The service behaviour is set to handle one request at a time, like so:

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

Earlier, after some "inspiration" (read: copy/paste) from the internet, I ended up adding a set of classes called something like XxxNHibernateUtil, for the auxiliary database and the legacy databases, respectively. These classes control the NHibernate Sessions and generate or re-use the Sessions from pre-initialized SessionFactories.

For the Auxiliary database it looks like this:

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();
        }
    }
}

Whenever a session is needed for a transaction, the current session is looked up and re-used for the duration of the service request call. Or at least: That is what is supposed to be happening.

EDIT: The session context is of course set in the hibernate.cfg.xml like so:

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

For the legacy databases, the NHibernateUtil is adapted to deal with different possible databases. For this, each connection gets its own SessionFactory built that has to be looked up in a Dictionary collection. Otherwise, the principles are the same.

Testing using WCFStorm, this seems to be working fine when sending one request at a time, but as soon I start a load test, even with just one agent and long intervals, I get a plethora of different kinds of exceptions all pointing to simultaneous requests and transactions sabotaging each other. I have tried tweaking the IsolationLevel, but of now avail.

I think I need to generate and handle Sessions in a different way, so that different transactions to the same databases are handled in an orderly fashion and not interfere with each other. However, I lack some insight in how to make this work. Any help is greatly appreciated!


EDIT For one service method, when testing with more than one agent, the first dozen or so call work fine, and then the following string of exceptions start appearing that only pertain the auxiliary database:

  1. "There was a problem converting an IDataReader to NDataReader" / "Invalid attempt to call MetaData when reader is closed."
  2. "illegal access to loading collection"
  3. "Begin failed with SQL exception" / "Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding."
  4. "could not execute query" / "ExecuteReader requires an open and available Connection. The connection's current state is closed."
  5. "could not initialize a collection:" / "Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding."
  6. "Transaction not successfully started"
  7. "The transaction is either not associated with the current connection or has been completed."
  8. "could not initialize a collection:" / "Invalid attempt to call Read when reader is closed."

Exception 1, at least, indicates that the same session is accessed by multiple threads (calls probably). Also the other ones indicate that the current session is interrupted by other processes. But how can this be, when I tried to isolate the calls and have them queued up?

For another service method, these issues do not appear with the auxiliary database, but after some time I start getting the ZombiedTransaction exceptions (deadlocks) with transactions to the legacy databases. Still... What gives?

解决方案

The easy answer: You don't re-use NHibernate sessions.

They're not heavyweight objects and they are designed to be created, manipulated and disposed following the Unit of Work pattern. Attempting to "share" these across multiple requests goes against their intended usage.

Fundamentally, the cost of synchronizing access to the sessions correctly will almost certainly negate any benefit you gain by recycling them to avoid re-initializing them. Let's also consider that these costs are a drop in the pond of the SQL you'll be executing.

Remember that NHibernate's Session Factory is the heavyweight object. It's thread-safe, so you can and should share a single instance across all requests out of the box.

Your code should look conceptually like this:

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
            ...
        }
    }
}

As an aside: Ideally, you'd be dependency-injecting the ISessionFactory into the service.

这篇关于NHibernate&amp; WCF:性能(会话重用)与并发(并发请求)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆