Windows Comunication Foundation WCF, ¿Como habilitar un cache?

Problemática

En un proyecto se está construyendo un ESB que tiene un despachador. Este despachador recibe las llamadas de las aplicaciones clientes y viendo en el catálogo de servicio resuelve que por cuales "filtros" hace pasar la llamada antes de invocar el servicio de Negocio que implementa la lógica que el cliente espera.

La idea es que el despachador arma un "Pipe" para cada servicio en el cual incluye los filtros que en el catálogo de servicios fueron configurados para ese servicio. Por ejemplo, un servicio puede tener los filtros de LOG y Medición de tiempos, mientras que otro servicio puede tener configurado LOG y Caché.

Para aumentar el desempeño hay que evitar que por cada llama a un servicio el despachador vaya a la base de datos del catálogo de servicios para armar el Pipe, si ya lo llamó no necesita ir a leer nuevamente cuales son los filtros que el pipe de ese Servicio contiene.

Aquí viene la pregunta ¿Cómo hago en WCF para poder mantener en memoria los Pipe de servicios que fueron llamados? Con la idea de evitar volver a leer la configuración.

 

"Solucionatica"

Bueno, está preocupación por la optimización de los tiempos de respuesta en el despachador es muy buena porque usando el concepto de Cache se pueden evitar consultas a la base de datos, reemplazándolas por consultas en memoria, lo que haría mucho más rápido el tiempo de respuesta.

WCF brinda opciones para poder implementar esto. Yo escogí incluir el Cache en el Host como propiedades. La clase Host es la que levanta los EndPoint en WCF y aloja los servicios. Todas las instancias de servicios que se ejecutan tienen acceso al Host, por lo que instalar ahí el cache se hace simple. Ahora, ojo con la concurrencia ya que las ejecuciones de las llamadas al servicio son concurrentes.

A.- Primero veamos la clase Pipe (Qué simula el Pipe del problema).

La clase Pipe es el recurso que queremos poder poner el en Cache de nuestro Servicio. Se muestra en el código uno la clase.

El código 1 muestra el elemento Pipe que será el recurso a almacenar en el Cache.

/// <summary>

/// Pipe de ejemplo, elemto a poner el en cache

/// </summary>

public class Pipe

{

private int _idServicio;

 

public int idServicio

{

get { return _idServicio; }

set { _idServicio = value; }

}

private DateTime fechaCreacion;

 

public DateTime FechaCreacion

{

get { return fechaCreacion; }

set { fechaCreacion = value; }

}

 

public Pipe(int IdServicio)

{

this._idServicio = IdServicio;

}

}

Código 1: Clase Pipe, elemento a poner cache

B.- Clase HostPuls.

Está clase extiende la clase ServiceHost de WCF para agregarlo propiedades que manejarán el Cache que estamos implementando.

La propiedad privada _CachePipe es una Lista de elementos del tipo Pipe. Es aquí donde se almacenarán los elementos Pipe que ya se conocen.

El método AddPipeCache agrega un elemento Pipe nuevo al Cache.

El método public Pipe BuscarPipe recibe el identificar del servicio y retorna el Pipe que le corresponde.

El código 2 muestra la clase HostPlus que es el Host extendido.

/// <summary>

/// Host de WCF especializado para mantener en Cache elementos

/// PIPE. Hereda de ServiceHost.

/// </summary>

public class HostPlus : ServiceHost

{

private int iIdServicioBusquedaCache = 0;

private int lala = 0;

public int Lala

{

get { return lala; }

set { lala = value; }

}

/// <summary>

/// Lista de elementos Pipe en Cache.

/// </summary>

private List<Pipe> _CachePipe = new List<Pipe>();

/// <summary>

/// Agrega de manera segura un elemento al Cache de PIPE’s

/// </summary>

/// <param name="nuevoPipe"></param>

public void AddPipeCache(Pipe nuevoPipe)

{

lock (this._CachePipe)

{

this._CachePipe.Add(nuevoPipe);

}

}

/// <summary>

/// Valida si el Pipe es del servivio identificado

/// por la propiedad iIdServicioBusquedaCache;

/// </summary>

/// <param name="Pipe">Elemento del Pipe</param>

/// <returns>Verdadero o falso </returns>

private bool BuscarPipe(wcfHolaMundo.HostMaster.Pipe Pipe)

{

return Pipe.idServicio == iIdServicioBusquedaCache;

}

/// <summary>

/// Implementa la busqueda de un Pipe para el Servicio.

/// </summary>

/// <param name="idServicio">Id del servicio</param>

/// <returns>Pipe que es de ese servicio</returns>

public Pipe BuscarPipe(int idServicio)

{

iIdServicioBusquedaCache = idServicio;

return this._CachePipe.Find(new Predicate<wcfHolaMundo.HostMaster.Pipe>(this.BuscarPipe));

 

}

/// <summary>

/// Limpia el Cache, lista de Pipe.

/// </summary>

/// <returns></returns>

public bool ClearPipeCache()

{

{

lock (this._CachePipe)

{

this._CachePipe.Clear();

}

}

return true;

}

/// <summary>

/// Constructor

/// </summary>

public HostPlus()

: base()

{

 

}

/// <summary>

/// Constructor

/// </summary>

/// <param name="singletonInstance"></param>

/// <param name="baseAddress"></param>

public HostPlus(object singletonInstance, params Uri[] baseAddress)

: base(singletonInstance, baseAddress)

{ }

/// <summary>

/// Constructor

/// </summary>

/// <param name="serviceType"></param>

/// <param name="baseAddress"></param>

public HostPlus(Type serviceType, params Uri[] baseAddress)

: base(serviceType, baseAddress)

{ }

 

}

Código 2: HostPlus

C.- El método del Servicio que recibe la llamada y valida en el cache.

El método "LlamarServicio(int idServicio)" es el método del servicio que recibe un identificador del servicio con el cual se busca en el cache. Si no se encuentra lo crea y agrega al cache para las siguientes llamadas.

¿Cómo una instancia del servicio tiene acceso al cache en el Host?

El acceso al Host se hace a través del "OperationContext". Esa clase tiene el método estático Current que permite acceder al contexto de la operación. Este contexto a su vez accede al Host usando este método "operationContext.Host". Ahora se debe hacer un CAST para pasar del tipo host a nuestro Host especializado el cual tiene el Cache implementado.

Una vez que tenemos nuestro Host podemos buscar en el Cache si el Pipe de ese servicio está almacenado. Para eso usamos myHost.BuscarPipe(idServicio).

Si se encuentra el Pipe, se responde con los datos de ese Pipe. Si no se encuentra se crea usando new wcfHolaMundo.HostMaster.Pipe(idServicio) y almacenando en el cache myHost.AddPipeCache(elPipe).

El siguiente código muestra el método completo del servicio.

/// <summary>

/// Este Servicio recibe el identificador

/// de un servicio y retorna un mensaje Nevo o Viejo

/// si es que existe o no en el Cache.

/// Si no existe lo crea en el cache.

/// </summary>

/// <param name="idServicio">Identificador </param>

/// <returns>Nevo o Viejo</returns>

public string LlamarServicio(int idServicio)

{

string strREspuesta = "";

///Contexto en el que se ejecuta el Servicio

OperationContext operationContext = OperationContext.Current;

///Instancia del Host

ServiceHost host = (ServiceHost)operationContext.Host;

///instanacia del HostPlus, con Cache implementado.

wcfHolaMundo.HostMaster.HostPlus myHost =

(wcfHolaMundo.HostMaster.HostPlus)host;

wcfHolaMundo.HostMaster.Pipe elPipe;

//Busca en el cache si el Pipe existe para un servicio

elPipe = myHost.BuscarPipe(idServicio);

if (elPipe == null)

{

///No existe Pipe para este Servicio.

///Se Crea.

elPipe = new wcfHolaMundo.HostMaster.Pipe(idServicio);

elPipe.FechaCreacion = DateTime.Now;

myHost.AddPipeCache(elPipe);

strREspuesta = "Nuevo";

}

else

{

///Existe Pipe para este Servicio

strREspuesta = "Viejo, creado el " + elPipe.FechaCreacion.ToString();

}

Console.WriteLine(idServicio.ToString() + ": " + strREspuesta);

return strREspuesta ;

}

Código 3: Método del Servicio.

De esta manera se ha implementado un Cache para aumentar el desempeño evitando en lo posible el tener que crear recursos ya conocidos por el servicio.

El código del Servicio, Host y cliente para probarlo se puede descargar desde aquí.

2 pensamientos en “Windows Comunication Foundation WCF, ¿Como habilitar un cache?

  1. Diego

    Muy bueno, Jotapé!!
     
    Quisiera aportar mis 5 centavos, algo que aprendí de un caché similar que habíamos implementado para un proyecto de banca. El hecho era que cada tanto había q relanzar la aplicación servidora (otros tiempos en que no se podía hacer deployment en caliente, y otras razones). Cuestión que el caché amanecía vacío en esa levantada a la espera de los primeros requerimientos
     
    A medida que los usuarios se empezaban a logonear y comenzaban a enviar requerimientos que necesitaban entrar al caché (para ahorrar tiempos, CPU, etc) se daba la paradoja de que llegaban pedidos similares, digamos como ejemplo "para el elemento K", con un intervalo tan breve entre ellos que no daban tiempo al servidor de construir el elemento K a cachear. De modo tal que el segundo requerimiento también se ponía a fabricar una instancia paralela de K -con el consiguiente consumo de recursos-. Para peor, no era que llegasen dos sino quizás cinco pedidos para K, otros cuatro para J, quizás seis para L y el servidor se guateaba feo para poder cumplir con todos en paralelo
     
    Para colmo de males se daba que cuando por fin un elemento se terminaba de armar, había que ponerlo en el caché pero -tal como tu código presenta- eso implicaba lockear el caché. Y como varios requerimientos concurrentes, que previamente habían estado armando instancias múltiples de un mismo objeto, necesitaban guardarlos en el caché al que se podía acceder de a uno, se generaba contención en el servidor
     
    En definitiva, aún creyendo que íbamos a ganar tiempo por haber puesto un caché te puedo asegurar que eran alrededor de 10 minutos posteriores a levantar el servidor que era un infierno inusable (y si quizás, a media mañana o al mediodía, había que relanzar la aplicación en alguno de los servidores de la granja, esto pasaba nuevamente)
     
    Al principio no entendíamos bien qué era lo que estaba pasando ya que sólo veíamos la punta del iceberg: los requerimientos de los clientes demoraban n en satisfacerse. Para colmo armar cada objeto K implicaba hacer un acceso a un CICS transaccional (un mainframe) con lo cual en ese lapso el servidor quedaba ocioso para ese thread. Por esto mismo no veíamos en realidad que los pedidos múltiples fueran la raiz del problema
     
    Entonces tomamos un torpe primer paso que fue extender la granja de servidores de cuatro a diez. Y digo "torpe" porque si bien ayudó a aliviar algo (ya que ahora bajaba la probabilidad de que pedidos similares vayan al mismo servidor), no alivió del todo: en efecto, los servidores de la granja se reparten la carga pero no comparten la memoria RAM. Cada uno tiene su propia caché con lo cual para que la contención no ocurra, ponele un servidor a cada usuario y listo. Alivió, pero fue una movida torpe
     
    Al menos nos sirvió para ver, con más claridad -en cámara lenta, por así decir- y sin la presión de los líderes usuarios que no dejaban de hacer sonar nuestros teléfonos, que efectivamente cada servidor estaba trabajando en actividades superfluas por una contingencia temporal de que la caché estaba más vacía de lo conveniente
     
    Lo resolvimos tratando de llenar la caché al levantar la aplicación con aquellos pedidos estadísticamente más probables. Nosotros no cacheabamos servicios, cacheabamos resultados de queries sobre tablas cód-descripción y no podíamos darnos el lujo de ponerlas todas en memoria
     
    Pero tu caso del ESB creo q es diferente y sí lo podría hacer. Por eso tú, al levantar la aplicación, podrías leer los servicios configurados -de los archivos de config o de donde fuere- y llenar el caché como parte del proceso de start up de tu aplicación
     
     
     
    Un abrazo y de nuevo gracias por ese post

    Responder
  2. Juan Pablo

    Dagum,
     
    como siempre tus comentarios son mas "grandes" en todas dimensiones que mis modestos POST😉
    Te cuento como nos va con esta idea cuando la pongamos en producción en Septiembre :D 

    Responder

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s