2012-01-05 22 views
5

Estamos trabajando en diferentes integraciones a un enjambre de bases de datos legados de idéntica estructura que básicamente no se pueden modificar. Para esto, agregamos una base de datos auxiliar para contener cosas como metainformación, reglas de enrutamiento y para almacenar temporalmente datos para las bases de datos heredadas.NHibernate y WCF: Rendimiento (reutilización de sesión) vs. concurrencia (solicitudes simultáneas)

Principalmente usamos NHibernate para conectarnos a las bases de datos. Una aplicación es un Servicio WCF que necesita insertar datos entrantes en tablas anidadas que son realmente anchas (docenas de columnas). Obviamente, el rendimiento es un problema, por lo que he estado buscando ser lo más económico posible con las transacciones de NHibernate. Al mismo tiempo, la concurrencia parecía ser un problema. En producción, comenzamos a tener algunos errores de transacción zombie (deadlocks).

He estado haciendo un acto de equilibrio para hacer frente a estos dos problemas, pero en realidad no han eliminado los problemas de simultaneidad.

El comportamiento del servicio se establece para manejar una solicitud a la vez, así:

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

antes, después de un poco de "inspiración" (léase: copiar/pegar) de la Internet, que terminó la adición de una conjunto de clases llamadas algo así como XxxNHibernateUtil, para la base de datos auxiliar y las bases de datos heredadas, respectivamente. Estas clases controlan las Sesiones de NHibernate y generan o reutilizan las Sesiones de los Fábrica de Sesión preinicializados.

Para la base de datos auxiliar que tiene el siguiente aspecto:

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

Siempre que se necesita una sesión para una transacción, la sesión actual se busca y reutilizada para la duración de la llamada de petición de servicio. O al menos: eso es lo que se supone que está sucediendo.

EDIT: El contexto de la sesión es, por supuesto establecido en el hibernate.cfg.xml así:

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

Para las bases de datos heredadas, la NHibernateUtil está adaptada para hacer frente a diferentes bases de datos posibles. Para esto, cada conexión tiene su propia construcción de SessionFactory que debe buscarse en una colección de diccionarios. De lo contrario, los principios son los mismos.

Probando usando WCFStorm, esto parece estar funcionando bien al enviar una solicitud a la vez, pero tan pronto como comienzo una prueba de carga, incluso con un solo agente y largos intervalos, recibo una plétora de diferentes tipos de excepciones apuntando a solicitudes simultáneas y transacciones saboteándose entre sí. He intentado ajustar IsolationLevel, pero ahora está disponible.

Creo que necesito generar y manejar Sesiones de una manera diferente, para que las diferentes transacciones a las mismas bases de datos se manejen de forma ordenada y no interfieran entre sí. Sin embargo, me falta algo de información sobre cómo hacer que esto funcione. ¡Cualquier ayuda es muy apreciada!


EDITAR Por un método de servicio, cuando se prueba con más de un agente, la primera docena de llamar funcionan bien, y luego la siguiente cadena de excepciones comenzarán a aparecer que sólo pertenecen la base de datos auxiliar:

  1. "Hubo un problema al convertir un IDataReader a NDataReader"/ "Intento no válido de llamar a MetaData cuando el lector está cerrado."
  2. "acceso ilegal a la colección de carga"
  3. "Error al comenzar con la excepción de SQL"/"Tiempo de espera caducado. El tiempo de espera transcurrido antes de la finalización de la operación o el servidor no responde".
  4. "no se pudo ejecutar la consulta"/"ExecuteReader requiere una conexión abierta y disponible. El estado actual de la conexión se cierra".
  5. "no se pudo inicializar una colección:"/"Tiempo de espera caducado. El tiempo de espera transcurrido antes de la finalización de la operación o el servidor no responde".
  6. "La transacción no se inició correctamente"
  7. "La transacción no está asociada con la conexión actual o se ha completado."
  8. "no se pudo inicializar una colección:"/"Intento no válido de llamar a Read cuando se cierra el lector."

La excepción 1, al menos, indica que se accede a la misma sesión por varios hilos (llamadas probablemente). Además, los otros indican que la sesión actual se ve interrumpida por otros procesos. Pero, ¿cómo puede ser esto, cuando traté de aislar las llamadas y hacerlas cola?

Para otro método de servicio, estos problemas no aparecen con la base de datos auxiliar, pero después de un tiempo empiezo a obtener las excepciones ZombiedTransaction (deadlocks) con transacciones a las bases de datos heredadas. Aún así ... ¿Qué da?

+0

Parece que se está ejecutando problemas de concurrencia. ¿Qué versión de NH estás usando? Si está utilizando 3.2, sugeriría usar "wcf_operation" como su contexto de sesión.Si no está usando 3.2 todavía hay una forma fácil de hacerlo. –

+2

También la afirmación 'throw ex;' anterior es un gran no-no. Estás destruyendo el rastro de la pila cuando haces eso. Deberías usar 'throw;' Ya que no estás haciendo nada con la excepción, ni siquiera debería haber un bloque try catch. –

+0

Lo siento. Pasé por alto tus comentarios. Estoy usando la versión 3.1, pero podría actualizar en un instante, ¡si eso ayudara! :-) ¡Y OK, arreglaré mi excepción de no-no y lo tendré en cuenta para el futuro! ¡Thnx hasta ahora! –

Respuesta

12

La respuesta fácil: No reutiliza las sesiones de NHibernate.

No son objetos pesados ​​y están diseñados para ser creados, manipulados y eliminados siguiendo el patrón de Unidad de trabajo. Intentar "compartir" estos en varias solicitudes va en contra de su uso previsto.

Fundamentalmente, el costo de sincronizar el acceso a las sesiones correctamente casi seguro negará cualquier beneficio que obtenga reciclándolos para evitar reinicializarlos. Consideremos también que estos costos son una gota en el estanque del SQL que ejecutará.

Recuerda que NHibernate's Session Factory es el objeto pesado. Es seguro para subprocesos, por lo que puede y debe compartir una única instancia en todas las solicitudes de forma predeterminada.

El código debería ser conceptualmente como esto:

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

Como acotación al margen: Idealmente, sería de dependencia de la inyección de la ISessionFactory en el servicio.

+1

También puede ser que valga la pena señalar que la sesión de NHibernate se utiliza para el seguimiento de la entidad, lo que podría provocar un comportamiento que se ve como pérdidas de memoria si se utiliza la misma sesión durante mucho tiempo (es decir, todas las entidades de la base de datos podría llegar a ser rastreados por la sesión) – Liedman

+0

Hmmm ... ¡Buen punto! Así que, básicamente, cambie el método GetCurrentSession a una sola línea: return GetSessionFactory(). OpenSession(); ? –

+0

Pero, ¿eliminará esto también los bloqueos? –

Cuestiones relacionadas