Por Jordi Villena
Publicado en enero 2014
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:
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
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:
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.
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.
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:
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:
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 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.
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.
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:
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.