martes, 19 de abril de 2011

Session en web garden


En un post anterior vimos a vista de pájaro cuales eran las
diferencias entre un web garden y un web farm.

Lo cierto es que ese post era totalmente teórico y ahora me gustaría que en este post viéramos realmente como implementar ambas soluciones.

La necesidad surge porque cualquier proyecto que pretenda ser escalable y tolerante a fallos, necesitará antes o después poder ser desplegado sin problemas en entornos de granjas web. Es decir, no se puede querer ganar escalabilidad y redundancia metiendo más máquinas si nuestro código no está preparado para trabajar en estos escenarios de despliegue.

El mayor problema que vamos a encontrar tanto en un web garden como en un web farm es que ambos trabajan en su propio y aislado espacio de memoria. Esto significa que los objetos Application, Session y Cache son independientes unos de otros. Es decir, los datos almacenados en la sesión de la máquina A no son los mismos que los almacenados en la sesión de la máquina B.

Lo cierto es que para el objeto Session (para Application y Cache habría que buscar soluciones alternativas), la solución pasa por guardarlo en base de datos. De este modo, el objeto Session será compartida por todas las máquinas en un web farm o procesadores en el caso de un web garden. Además y como característica extrá un reinicio de la aplicación no supondrá que se pierdan los datos de sesión. Para guardar la sesión en base de datos, la principal consideración es que todo lo que queremos guardar tiene que ser serializable.

Antes de ver cómo guardar la sesión en base de datos, veamos primero el problema y así seremos conscientes de que existe.

Nuestro ejemplo será una página sencilla donde podremos incrementar un valor tanto para Application, como para Session y Cache. Como vamos a empezar por un web garden, también mostraremos en pantalla el PID del IIS Worker Process que está atiendo actualmente nuestra petición.

Para crear un web garden hay que incrementar el número de IIS Worker Process que atienden al grupo de aplicaciones (application pool) en el que se ejecuta nuestra aplicación. Seguro que habrá más opciones relativas a un web garden que se puedan configurar pero inicialmente sólo con esto ya serviría al propósito de este post.

clip_image002

Si ahora ejecutamos nuestra aplicación veremos cuales son los resultados.

A este respecto cabe mencionar que no he encontrado ninguna pauta concreta sobre cuando IIS atiende una petición en un proceso o en otro. De hecho, a veces en mi misma máquina sólo utilizó un proceso, después dos, si un compañero también pedía la página desde su máquina le atendía desde otro proceso, etc.

En cualquier caso (y después de haber hecho clic 100 veces de forma compulsiva) los resultados son los siguientes:

clip_image003

clip_image004

clip_image005

Como vemos, hay 2 procesos con distinto PID atendiendo la misma aplicación. Además vemos como los valores de Application, Session y Cache son distintos en ambos. Está claro que tenemos un problema si queremos desplegar nuestra aplicación en un entorno de web garden y utilizar Application, Session o Cache.

El primer problema que vamos a solucionar es el objeto Session (y de hecho el único que trataremos en este post). Para ello, vamos a guardar la sesión en base de datos (también sería igualmente válido guardar la sesión en un servidor de estado). En cualquiera de los casos la sesión será compartida por múltiples procesos (web garden) o incluso máquinas (web farm) y además no se perderá si se reinicia la aplicación.

Para más información sobre las distintas opciones disponible para guardar el objeto Session, puedes visitar http://msdn.microsoft.com/es-es/library/ms178586(v=vs.80).aspx y http://support.microsoft.com/kb/317604/EN-US

Para guardar la sesión en base de datos lo primero que necesitamos es una estructura de tablas adecuada. Para ello primero he creado una base de datos llamada “EjemploWebFarm” y después ejecutaremos el siguiente comando:

aspnet_regsql.exe –S NombreServidor –U NombreUsuario –P Password –ssadd –sstype c –d EjemploWebFarm

clip_image007

A grandes rasgos le hemos dicho que cree todos los objetos necesarios (tablas, procedimientos almacenados, etc.) en nuestra base de datos “EjemploWebFarm”.

También se puede guardar en la base de datos tempdb (pero se perdería la sesión si se reinicia el servidor SQL) o en la base de datos ASPState (imagina que tienes varias aplicaciones utilizando este sistema en un mismo servidor y quieres agrupar a todas ellas en una sola base de datos).

Nosotros hemos optado por la tercera vía que es utilizar nuestra propia base de datos.

La verdad es que nos ha metido un buen número de procedimientos almacenados, tipos definidos por el usuario y un par de tablas: ASPStateTempApplications y ASPStateTempSessions.

Teniendo ya el soporte donde guardar la sesión sólo queda configurar nuestro fichero web.config para indicar que queremos guardar allí el objeto Session. Esto se consigue a través del elemento sessionState.

<?xml version="1.0"?>

<configuration>

  <system.web>

    <compilation debug="false" strict="false" explicit="true" targetFramework="4.0"/>

    <sessionState

      allowCustomSqlDatabase="true"

      mode="SQLServer"

      sqlConnectionString="Data Source=LOBEZNO;Initial Catalog=EjemploWebFarm;Persist Security Info=True;User ID=sa;Password=********"/>

  </system.web>

</configuration>

Si ahora ejecutamos de nuevo nuestra aplicación podemos ver que el objeto Session ya está compartido entre los distintos IIS Worker Process de nuestro web garden. Por otro lado, si vemos el contenido de las tablas podemos observar que en ASPStateTempApplications ha metido 1 registro con el campo AppName a la ruta de IIS que identifica nuestra aplicación.

clip_image008

Y que en ASPStateTempSessions ha metido 1 registro cuyo campo SessionId es el mismo valor que vemos en la cookie ASP.NET_SessionId. Es así como se sabe que valores de sesión guardados en esta tabla corresponden con que sesiones en concreto.

Si por algún motivo no tenemos un MS SQL Server disponible para guardar la sesión también podemos utilizar un servidor de estado. Esta opción es igualmente considera fuera de proceso (out of proccess), por lo que nuestros datos de sesión también serán compartidos en entornos de multi-procesador o multi-servidor.

Al instalar ASP.NET se instala un servidor de estado como un servicio (por defecto está detenido). En concreto es el fichero aspnet_state.exe quien da el servicio. Los cambios para guardar nuestra sesión en este servidor son mínimos y todos ellos a través del fichero web.config, que ahora queda de la siguiente forma:

<?xml version="1.0"?>

<configuration>

  <system.web>

    <compilation debug="true" strict="false" explicit="true" targetFramework="4.0"/>

    <sessionState

      mode="StateServer"

      sqlConnectionString="tcpip=LOBEZNO:42424"/>

  </system.web>

</configuration>

 

Lógicamente también tenemos que iniciar el servicio pertinente:

clip_image010

Si ejecutamos de nuevo nuestra aplicación  el resultado es el deseado, es decir, se están compartiendo los datos de sesión.

Queda pendiente como solucionar los objetos Application y Cache, pero eso será otro día.

Un saludo!.

5 comentarios:

  1. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  2. Eres Dios Tio! Muchas gracias por el artículo. Me has aclarado justo la duda que tenía. Chapó!

    ResponderEliminar
  3. Gracias Ramón!
    Un comentario positivo me sube el karma ;-)

    ResponderEliminar
  4. Hola

    Tengo un problema: esquema web1-app1 web2-app2, cuando un usuarioA ingresa en web1-app1 y cierra su sesion supuestamente y otro usuarioB ingresa en la misma maquina del usuarioA este ve la informacion de el usuarioA y la del usuarioB.

    La implementacion de Web Garden seria una buena opcion para remediar esto?

    ResponderEliminar
  5. Hola Servicios IT, la verdad es que el caso que cuentas no me ha pasado nunca. Ni con web garden ni sin web garden, es decir, que un usuario acceda a la sesión de otro usuario es la primera vez que lo oigo. Si pudieras dar más datos quizás podríamos buscar una solución. Un saludo.

    ResponderEliminar