Compartir parte de la sesión http entre aplicaciones web desplegadas en diferentes clusters WebLogic usando Coherence*Web

Por Jordi Villena
Publicado en enero 2014

Configuración estándar

Coherence*Web permite descargar al servidor de aplicaciones WebLogic de la gestión y almacenamiento de la sesión de usuario, haciendo que éste pueda dedicarse exclusivamente a atender las peticiones de usuario. Esto es posible siempre y cuando lo que queramos almacenar en Coherence sea la sesión completa de las aplicaciones involucradas.

Esta configuración es posible siempre que el tamaño de las sesiones http de cada una de las aplicaciones implicadas no sea muy grande, de forma que el sobrecoste de serializar/transmitir/deserializar atributos, no incremente considerablemente el tiempo de respuesta al usuario. En estas situaciones, será Rrecommendable seleccionar los atributos de sesión que serán almacenados en la cache distribuida, y cuáles serán almacenados en el propio servidor de aplicaciones.

Coherence*Web proporciona mecanismos para manejar la gestión de la sesión en un amplio espectro de posibilidades, de forma que es posible compartir parte de la sesión http entre las diferentes aplicaciones, sin tener que acarrear con la perdida de rendimiento por el hecho de designar la gestión completa de las sesiones de usuario en JVM remotas.

La forma en la que Coherence*Web nos permite seleccionar que atributos serán almacenados en una cache distribuida, y que atributos residirán en una cache local a la instancia WebLogic que los ha creado, es utilizando una implementación específica del interfaz SessionDistributionController. Dicha implementación define el patrón que se seguirá para decidir si la sesión, o atributos específicos de la sesión, serán almacenados en la cache distribuida, o en una cache local a la instancia de WebLogic en la que se procesa la petición.

El objetivo de delegar el almacenamiento y gestión de determinados atributos de sesión en Coherence es principalmente para reducer la carga al servidor de aplicaciones, pero en la mayoría de los casos el objetivo es el poder compartir información entre diferentes aplicaciones web, independientemente de donde ellas residan. Coherence*Web nos permite beneficiarnos de esta característica sin prácticamente tener que cambiar una sola línea de código.

El siguiente ejemplo muestra una implementación que mandará los atributos cuyo nombre comience por “shared_” a la cache distribuida, y el resto los persistira en un almacenamiento local a la instancia WebLogic que atiende la petición de usuario. En cualquier caso las sesiones serán gestionadas por Coherence:

package com.beax.cohweb;

import com.tangosol.coherence.servlet.HttpSessionCollection;
import com.tangosol.coherence.servlet.HttpSessionModel;
import com.tangosol.coherence.servlet.HttpSessionCollection.SessionDistributionController;

public class CustomSessionDistributionController implements
		SessionDistributionController {

	@Override
	public void init(HttpSessionCollection arg0) {
	}

	@Override
	public boolean isSessionAttributeDistributed(HttpSessionModel arg0, String arg1) {
		// Distribute attributes starting by shared_
		return arg1.startsWith("shared_");
	}

	@Override
	public boolean isSessionDistributed(HttpSessionModel arg0) {
		// All the sessions will be distributed
		return true;
	}
}

Además de la implementación del SessionDistributeController, esta tendrá que ser referenciada en las aplicaciones Web que vayan a hacer uso de ella, de una de las siguientes formas:

1. Como argumento en el arranque de la instancia WebLogic.

coherence.distributioncontroller.class=com.beax.cohweb.CustomSessionDistributionController

2. Como parámetro en el descriptor web.xml (context-param):

  <context-param>
    <param-name>coherence-sessioncollection-class</param-name>
    <param-value>com.tangosol.coherence.servlet.SplitHttpSessionCollection</param-value>
  </context-param>

El uso de una implementación específica de un SessionDistributeController requiere de una configuración extra que asegure la sincronización de la información en cualquier condición, y la visibilidad de los atributos almacenados localmente en las instancias WebLogic. Esta configuración incluye los siguientes puntos:

  • Habilitar mecanismo de bloqueo (a nivel de aplicación o de JVM). Este mecanismo de bloqueo puede generar un overhead que en determinadas situaciones puede no estar justificado.
  • Deshabilitar la topology Near en las caches de Coherence*Web.
  • Deshabilitar la Coherence Sticky Optimization.
  • Habilitar storage en los nodos que corresponda: Los atributos de sesión a compartir solo han de residir en los nodos Coherence dedicados, esto es los que se ejecutan OOP:
Storage configuration for WebLogic nodes
tangosol.coherence.session.localstorage=false
tangosol.coherence.distributed.localstorage=false
Storage configuration for Coherence Processes
tangosol.coherence.session.localstorage=true
tangosol.coherence.distributed.localstorage=true

Configuración específica

El escenario que se ha mostrado puede parece un tanto extraño, pero analizándolo en detalle podemos ver que cumple con un requerimiento bastante común en entornos de middleware, básicamente la necesidad de compartir información entre diferentes aplicaciones independientemente de donde se hospeden dichas aplicaciones, permitiendo la selección de la información a compartir.

Este ha sido el caso del último cliente para el que he trabajado, donde hemos configurado un clúster de instancias dedicadas Coherence para actuar como cache distribuida, que sería accedido por diferentes aplicaciones Web desplegadas en diferentes clústeres de WebLogic.

Esta tipo de configuración, cuando ha de involucrar aplicaciones desplegadas en diferentes clústeres de WebLogic, presenta algunas limitaciones que requieren de una configuración un tanto particular. Esta es:

  • Deshabilitar Coherence Sticky Optimizations
  • Asegurar que la topología Near no es utilizada para las caches que utiliza Coherence*Web.
  • Manejo de cookies de sesión de aplicaciones que han de compartir información de sesión.

Deshabilitar Coherence Sticky Optimizations

El uso de una implementación de Using a Custom Session Distribute Controller (CSDC), implica habilitar una optimización in Coherence*Web para la limpieza automática de sesiones de usuario en instancias de WebLogic que no deberían atender nuevas peticiones de usuario asociadas a una sesión específica. Esta optimización, en nuestro caso, limpia objetos de session innecesarios que si pueden ser necesarios en el caso de que diferentes peticiones de usuario vayan saltando entre las diferentes aplicaciones web desplegadas entre diferentes clústeres de WebLogic. Es por este motivo que esta optimización ha de ser deshabilitada.

Determinadas versiones de Oracle Coherence indican que el parámetro coherence-sticky-sessions ha de ser habilitado siempre que se utilice una implementación de Session Distribute controller. No es el caso desde la versión 3.7.11, siendo esta la versión mínima recomendada en caso de tener que aplicar una configuración similar a la relatada en este artículo.

Este parámetro habilita algunas optimizaciones que mejoran la gestión de memoria en las instancias WebLogic cuando una implementación de SessionDistributeController es usada, limpiando cualquier atributo de sesión almacenado localmente en las instancias WebLogic, tan pronto una nueva petición de usuario es atendida por una nueva instancia de WebLogic.

En nuestro escenario se plantea aceptar peticiones de usuario desde diferentes clusters de WebLogic, de forma que diferentes instancias WebLogic atenderán concurrentemente peticiones de usuario asociadas a una misma sesión. Así que dicha optimización se deshabilitará para prevenir la limpieza de atributos de sesión antes de lo debido.

Asegurar que la topología Near no es utilizada por caches de Coherence*Web

El uso de la tolpología Near de Coherence, no puede asegurar que todas las copias de los atributos de sesión sean actualizados de forma síncrona en situaciones extremas*1. Para asegurar que la topología Near no es utilizada el siguiente parámetro tiene que ser deshabilitado (JAVA_OPTION):

En caso de que el parámetro anterior no prevenga la creación de copias locales de objetos de sesión distribuidos en la cache frontal (front), el siguiente cambio ha de hacerse en el fichero session-cache-config.xml utilizado por todas las instancias WebLogic involucradas en la compartición de sesión:

Change this piece of the xml:
    <!--
    The clustered cache used to store Session attributes.
    -->
    <cache-mapping>
      <cache-name>session-storage</cache-name>
      <scheme-name>session-near</scheme-name>
    </cache-mapping>

To this:
    <!--
    The clustered cache used to store Session attributes.
    -->
    <cache-mapping>
      <cache-name>session-storage</cache-name>
      <scheme-name>session-distributed</scheme-name>
    </cache-mapping>

Note:

*1 Situación extrema: Situación en la que un atributo de sesión sea solicitado por una instancia WebLogic mientras está siendo actualizado por otra instancia WebLogic. La topología Near dispone de un mecanismo basado en eventos que asegura que todas las copias de un determinado objeto sean sincronizadas al mismo tiempo, pero dicho mecanismo, en caso de sobrecarga, no puede asegurar que todas las copias de un objeto sean sincronizadas antes de que sean solicitadas por otra instancia WebLogic.

Manejo de Cookies de sesión de aplicaciones que han de compartir la sesión

Coherence*Web puede ser utilizada para compartir sesiones de usuario entre aplicaciones desplegadas en diferentes clústeres de WebLogic, pero en los casos en los que solo una parte de la sesión va a ser compartida entre dichas aplicaciones, parte de la gestión de las cookies de sesión de las aplicaciones involucradas ha de ser ajustada. La razón por la que es necesario realizar dicho ajuste es porque la cookie de sesión generada por WebLogic es utilizada a diferentes niveles. Estos son:

  • En capa de frontal Web: el Proxy plug-in de WebLogic lee la cookie de sesión proporcionada por el navegador para identificar en que instancias de WebLogic se hospeda la copia primaria/secundaria de la sesión.
  • En capa WebLogic/Coherence: Cuando la petición alcanza la instancia WebLogic correspondiente, si Coherence*Web ha sido configurado, Coherence leerá nuevamente la información de la cookie para identificar que sesión de usuario está asociada con la cookie,  ty en caso de que se esté utilizando una implementación de SessionDistributeController, cuando un atributo de sesión tenga que ser recuperado:
    • Si el atributo de sesión no es distribuido, esté será leído/recuperado en las caches locales de la instancia WebLogic.
    • Si el atributo de sesión es distribuido, éste será leído/recuperado de las caches distribuidas de Coherence*Web.

Como es utilizada la cookie de WebLogic por Coherence*Web

Coherence*Web utiliza la cookie generada por WebLogic server para identificar la sesión de usuario asociada a una determinada petición de usuario. Ésta cookie es manejada de la siguiente forma:

1. WebLogic Server adjunta la cookie de sesión en la respuesta enviada al navegador en la primera petición de usuario asociada a una determinadas sessión http. El contenido de dicha cookie incluye la siguiente información:

<SESSION_ID>!<JVM_ID1>!<JVM_ID2>

Donde:

  • <SESSION_ID>: Este identificador es utilizado para localizar la sesión de usuario cuando la petición de usuario llega a una instancia WebLogic.
  • <JVM_ID1>: Identificador utilizado por el proxy plug-in de WebLogic instalado en el frontal web para localizar la instancia WebLogic en la que se hospeda la copia primaria de la sesión de usuario.
  • <JVM_ID2>: Identificador utilizado por el proxy plug-in de WebLogic instalado en el frontal web para localizar, en entornos en los que la réplica de sesión  ha sido habilitada, la instancia WebLogic en la que se hospeda la segunda copia de la sesión de usuario. Este identificador será utilizado en caso de que la instancia en la que se encuentra la copia primaria de la sesión, no haya podido ser alcanzada.

2. Peticiones de usuario no alterarán el valor del <SESSION_ID>. Y los identificadores <JVM_ID1> y <JVM_ID2> solamente serán actualizado en el caso de que una instancia WebLogic caiga, o no sea visible por el frontal Web.

3. La cookie es utilizada a dos niveles:

  • El Proxy plugin instalado en el frontal Web utiliza el JVM Identifier almacenado en la cookie para redirigir cada petición de usuario a la instancia de WebLogic que hospeda la copia primaria de la sesión de usuario.
  • Cuando la petición de usuario llega a una instancia WebLogic, dicha instancia identifica la sesión de usuario utilizando el Session Identifier almacenado en la cookie.

El problema

Piense en el caso en el que una petición de usuario es redirigida a una instancia de WebLogic de un determinado clúster, y subsiguientes peticiones son redirigidas a otra instancia de WebLogic desplegada en un clúster diferente.

  • Utilizando la misma cookie de sesión entre las diferentes aplicaciones que han de compartir parte de los atributos de sesión:
    • Coherence*Web será capaz de identificar que sesión es la que corresponde a las diferentes peticiones de una misma sesión, independientemente de la aplicación a la que vayan dirigidas
    • El proxy plug-in instalado en el frontal web NO será capaz de identificar que instancia WebLogic del clúster en el que una de las aplicaciones está desplegado, ya que cada vez que una petición de usuario es redirigida a aplicaciones desplegadas en un clúster diferente, la parte de la cookie que contiene la información referente a la instancia WebLogic en la que se hospeda la sesión de usuario (JVM identifier), es sobreescrita con la información de la última instancia WebLogic que ha atendido una petición de usuario asociada a la sesión, perdiendo la referencia a cualquier otra instancia WebLogic (de otros clústers).

Este comportamiento puede observarse realizando peticiones a diferentes aplicaciones desplegadas en dos cluster WebLogic diferente, utilizando una implementación propia de SessionDistributeController que almacene parte de la sesión en las instancias WebLogic, e intentando acceder a los atributos almacenados localmente. De esta forma podremos observar como los atributos locales no son recuperados correctamente cuando saltamos entre instancias WebLogic de los diferentes clústers.

  • Utilizando diferentes cookies de sesión entre las aplicaciones en las que queremos compartir parte de la sesión: Esta configuración no nos permite correlacionar las sesiones de las diferentes aplicaciones desplegadas en los diferentes clústeres.
 
 
 
 

La solución

El escenario mostrado y los requerimientos antes explicados, nos llevan a una situación en la que la gestión actual de cookies no soporta nuestra casuística. Esta es: compartir “parte” de la sesión entre aplicaciones J2EE desplegadas en diferentes clústeres.

La forma más simple de solventar este problema es almacenando toda la sesión en la cache distribuida, pero esto en muchos casos puede tener un impacto negativo, o simplemente no ser posible. En caso de que esta configuración fuese posible, tendríamos que utilizar la característica Session Attribute Scoping de Coherence*Web para discriminar los atributos que han de ser visible entre las diferentes aplicaciones, de los que no.

La otra forma de solucionar este problema, y probablemente la que mejor se ajusta en la mayoría de los casos, consiste en configurar diferente cookies de sesión en cada una de las aplicaciones que han de compartir parte de la sesión, y asegurar  que todas ellas utilizarán el mismo identificador de sesión. Podemos conseguir este comportamiento generando copias de la cookie generada de forma automática por WebLogic asociadas al nombre de cookie configurada para cada una de las aplicaciones que queramos que compartan la sesión de usuario. Estas copias serán generadas en la primera petición de usuario realizada hacia cualquiera de las aplicaciones configuradas para compartir la sesión:

 

Ejemplo de configuración

A continuación se muestran los artefactos y configuración necesarios para realizar la configuración expuesta:

1. Configurar cada aplicación para utilizar su propia cookie de sesión: Editar el descriptor weblogic.xml de cada una de las aplicaciones Web, especificando un valor de cookie name diferente para cada una de las aplicaciones:

<wls:session-descriptor>
        <wls:timeout-secs>3000</wls:timeout-secs>
        <wls:cookie-name>WLS1SID</wls:cookie-name>
        <wls:cookie-path>/</wls:cookie-path>
        <!-- wls:cookie-max-age-secs>60</wls:cookie-max-age-secs> -->
        <wls:persistent-store-type>MEMORY</wls:persistent-store-type>
	</wls:session-descriptor>

2. Instalar un J2EE Session Filter para realizar la copia y “adjuntado” de las cookies asociadas a cada una de las aplicaciones involucradas. Dicho Session filter será el responsable de identificar cuando las diferentes cookies relacionadas con las diferentes aplicaciones Web han de ser adjuntadas al response:

CODE OF THE SERVLET FILTER

package com.beax.sample.cohweb;

import java.io.IOException;
import java.util.StringTokenizer;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet Filter implementation class CookieFilter
 */
public class CookieFilter implements Filter {

   public CookieFilter() {
   }

   public void destroy() {
   }

   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
   throws IOException, ServletException {

      // add cookies if needed
      cookieWork(request, response);

      // pass the request along the filter chain
      chain.doFilter(request, response);
   }

   public void init(FilterConfig fConfig) throws ServletException {
   }

   /*
    * Add cookies for any related environment
    */
   private void cookieWork(ServletRequest request, ServletResponse response) throws ServletException, 
   IOException {
      String localCookieName = System.getProperty("cookie.name");
      StringTokenizer cookieList = new StringTokenizer(System.getProperty("cookie.list"), ";");
      HttpServletRequest myReq = (HttpServletRequest) request;
      HttpServletResponse myResp = (HttpServletResponse) response;

      Cookie [] cookies = myReq.getCookies();

      HttpSession ses = myReq.getSession(true);
      String sessId = ses.getId();

      String cookie_sessId = null;

      while(cookieList.hasMoreElements()) {
         boolean createCookie = true;
         String nextCookie = (String)cookieList.nextElement();
         //  
         // Only create cookies that are not created automatically by WLS. 
         //  
         if (nextCookie.equals(localCookieName)) {
            createCookie= false;
         } else {
            if (cookies != null) {
               // 
               // Check existing cookies: 
               //  - If any cookie of the "cookie.list" does not exist or 
               //    if the sessId does not match, cookie must be created.
               // 
               for(int j=0; j<cookies.length; j++) {
                  Cookie cookie = cookies[j];
                  if (cookie.getName().equals(nextCookie)) {
                     int sep = cookie.getValue().indexOf("!");
                     if (sep > 0) {
                        cookie_sessId = cookie.getValue().substring(0, sep);
                        if (sessId.startsWith(cookie_sessId)) {
                           createCookie = false;
                           break;
                        }
                     }
                  }
               }
            } else {
               // 
               // Create cookie if it does not exist
               // 
               createCookie = true;
            }
         }
         if (createCookie) {
            Cookie cookie = new Cookie(nextCookie, sessId + "!"); 
            cookie.setPath("/");
            //cookie.setMaxAge(60);
            myResp.addCookie(cookie);
         }
      }
   }
}

REFERENCE TO THE SERVLET FILTER IN web.xml DESCRIPTOR
  <filter>
    <display-name>CookieFilter</display-name>
    <filter-name>CookieFilter</filter-name>
    <filter-class>sample.coherence.CookieFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>CookieFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>



Notas:

  • Las cookies a ser adjuntadas no han de definir un expiration time. De esta forma la vida de la cookie será la misma que la vida de la sesión relacionada.
  • Cookie Path ha de coincidir con el Path utilizado en la cookie generada de forma automática por la instancia WebLogic.
  • La forma de dar al Servlet Filter la información relacionada con las cookies a ser adjuntadas puede ser proporcionada de diferentes maneras. Como ejemplo, esta información puede ser proporcionada como parámetros de inicio en las instancias WebLogic:
WebLogic Cluster 1 nodes
set JAVA_OPTIONS=%JAVA_OPTIONS% -Dcookie.name=WLS1SID
set JAVA_OPTIONS=%JAVA_OPTIONS% -Dcookie.list=WLS1SID;WLS2SID;WLSNSID

WebLogic Cluster 2 nodes
set JAVA_OPTIONS=%JAVA_OPTIONS% -Dcookie.name=WLS2SID
set JAVA_OPTIONS=%JAVA_OPTIONS% -Dcookie.list=WLS1SID;WLS2SID;WLSNSID

WebLogic Cluster N nodes
set JAVA_OPTIONS=%JAVA_OPTIONS% -Dcookie.name=WLSNSID
set JAVA_OPTIONS=%JAVA_OPTIONS% -Dcookie.list=WLS1SID;WLS2SID;WLSNSID

Durante el evento de inicio de sesión para la aplicación que utiliza la cookie WLS1SID, el J2EE Session Filter adjuntará las cookies de sesión para el resto de las aplicaciones, y vice-versa. Este componente asegurará que todas las aplicaciones relacionadas utilizarán el mismo session-id:

First request to an applicative resource served by the application which uses cookie WLS1SID

- Cookie autogenerated by WebLogic Server:

WLS1SID: bhYgZacv61QaJ6Gh2SV9MjtaTu3BdIoE1T98i2rNJB2y4XsypU3r!-202330354!1365442637731

- Cookies generated by the developed Servlet Filter:

WLS2SID: bhYgZacv61QaJ6Gh2SV9MjtaTu3BdIoE1T98i2rNJB2y4XsypU3r!-202330354!1365442637731
WLSNSID: bhYgZacv61QaJ6Gh2SV9MjtaTu3BdIoE1T98i2rNJB2y4XsypU3r!-202330354!1365442637731

    

Subsiguientes requests redirigidas a aplicaciones que utilizan las cookies WLS2SID o WLSNSID actualizarán los identificadores de JVM de sus correspondientes cookies:

First request to a resource served by the application which uses cookie WLSNSID

- Updated cookie by WebLogic Server:

WLSNSID: bhYgZacv61QaJ6Gh2SV9MjtaTu3BdIoE1T98i2rNJB2y4XsypU3r!934311264!1365442637731

 


Publicado por Jordi villena. Agradecimientos a los compañeros Jonathan Birch y Stephano Mantini por aportar muy buenas ideas a la hora de implementar esta solución.