为 OTN 撰稿
为 Oracle 技术网撰写技术文章,获得报酬的同时可提升技术技能。
了解更多信息
密切关注
OTN 架构师社区
OTN ArchBeat 博客 Facebook Twitter YouTube 随身播图标

Coherence*Web:不同 Oracle WebLogic 集群中的应用程序共享一个 httpSession

作者:Jordi Villena

轻松扩展 Coherence*Web 以支持会话共享。

2013 年 10 月

下载
download-icon13-1Oracle Coherence
download-icon13-1Oracle WebLogic

标准配置

Coherence*Web 支持对整个会话进行分流,这样 Oracle WebLogic 实例便可专心处理 Oracle WebLogic 请求;然而在某些情况下,http 会话比较大,需要权衡是分流整个用户会话,还是仅分流需要在不同 Web 应用程序之间共享的对象以减少网络占用。Coherence*Web 和 Oracle Coherence 本身支持低延迟的非阻塞式 I/O,不过在处理大量数据时仍需要花费时间和带宽进行传输。在这种情况下,行之有效的办法是能够辨别哪些会话属性必须存储在 Oracle WebLogic 实例的本地存储中,哪些会话属性应存放在 Oracle Coherence 分布式缓存中。

Coherence*Web 中的存储支持各种不同的配置方式。一方面需要分流 Oracle WebLogic 实例,另一方面将整个 http 会话存储在一个 Oracle Coherence 专用节点中又有助于减少性能损失。为了在二者之间达到平衡,可以通过自定义会话分配控制器将会话属性的一个子集存储在 Oracle Coherence 外部缓存中。

之所以从 Oracle WebLogic 中分流一些会话属性,另一个原因是要与部署在其他 Oracle WebLogic 集群中的 Web 应用程序共享这组属性。借助会话分配控制器,我们可以添加逻辑来判定一个会话属性是否必须存储在分布式缓存中。

以下示例展示了一个会话分配控制器,它将以“shared_”开头的会话属性存储在分布式 Coherence 层中,将其他属性存储在负责处理用户请求的 Oracle WebLogic 托管实例的本地存储中:

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

创建自定义会话分配控制器之后,必须将其添加到所有将使用它的 Web 应用程序中。此外,还需要以 JAVA OPTION 的形式将其引用添加到部署了 Web 应用程序的 Oracle WebLogic 实例中:

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

也可以在 web.xml 描述符文件中以上下文参数的形式指定此参数:

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

使用 sessionDistributionController 时需要通过额外的配置来确保共享信息的同步,并且确保本地存储的会话属性的可见性。为此,必须进行特定的 Oracle Coherence 配置;其要求如下:

  • 在应用程序或成员级别启用锁定机制
  • 对 Coherence*Web 缓存禁用 Coherence 邻近拓扑
  • 禁用 coherence-sticky-sessions 优化
  • 在适当的节点启用存储:由于待分配的会话属性只能位于 Coherence 专用节点中,因此应只启用这些节点中的存储:
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

巧妙的配置

尽管乍一看很奇怪,不过下面这种情况确实很常见:您只需在不同 Oracle WebLogic 集群中部署的一些应用程序之间共享一小部分会话属性。

我上次负责的一位客户就希望配置一个进程外 Coherence 集群来存储不同 Oracle WebLogic 集群中部署的多个 Web 应用程序的一些会话属性。其目的是为了在不同 Web 应用程序之间共享这些属性。

由于这一配置需要覆盖不同 Oracle WebLogic 集群中部署的多个应用程序,因此有一些限制是 Coherence*Web 目前无法解决的:

  • 禁用 coherence-sticky-sessions 优化
  • 确保未使用 Coherence 邻近拓扑
  • 通过 Cookie 管理将会话扩展至多个 WebLogic 集群

禁用 Coherence-Sticky-Sessions 优化

使用自定义会话分配控制器 (CSDC) 进行优化后,Coherence*Web 可以清理 Oracle WebLogic Server (WLS) 节点中不需要进一步处理特定用户会话请求的会话。使用 Coherence*Web 共享部署在不同 Oracle WebLogic 集群中的多个应用程序的会话属性时,这项优化会清理其他用户请求需要的一些用户会话对象,因此必须禁用此优化。

Oracle 文档指出,使用自定义会话分配控制器时必须将 coherence-sticky-sessions 参数(粘滞会话优化)设为“ture”。

使用 sessionDistributionController 时,此参数会启用一些优化来增强 WLS 节点中的内存管理。因此,当新请求重定向至不同 WLS 会话时,它会清理 WLS 中存储的会话的所有属性。在我们的场景中,由于我们计划接受来自不同 WLS 集群的请求,因此不同 WLS 将针对同一用户会话发出用户请求;这样一来,为了避免在需要之前清理 WLS 节点中的会话,必须禁用此优化(请勿设为“true”)。

villena-coherence-web-sharing-fig01.png
图 1

确保未使用 Coherence 邻近拓扑

如果使用 Coherence 邻近拓扑,Coherence 在某些边界情况下将无法确保所有副本保持同步 [1]。为了确保未使用 Coherence 邻近拓扑,需要将以下参数设为“false”:

coherence.session.optimizeModifiedSessions=false

注:边界情况
某个会话属性由一个 Oracle WebLogic 实例请求,但由另一个 Oracle WebLogic 实例更新。邻近拓扑采用一种事件机制系统确保对象的所有副本立刻同步。然而,在过载的情况下,当使用 Coherence 邻近拓扑通过前端 Web 层的不同节点访问对象的副本时,这种事件机制无法确保对象的所有副本是最新的。

如果之前的参数不阻止为 WLS 节点本地存储中的对象创建本地副本,那么应对 WLS 节点使用的 session-cache-config.xml 文件进行如下更改:

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>

注:
边界情况:某个会话属性由一个 Oracle WebLogic 实例请求,但由另一个 Oracle WebLogic 实例更新。邻近拓扑采用一种事件机制系统确保对象的所有副本立刻同步。然而,在过载的情况下,当使用 Coherence 邻近拓扑通过前端 Web 层的不同节点访问对象的副本时,这种事件机制无法确保对象的所有副本是最新的。

通过 Cookie 管理将会话扩展至多个 WebLogic 集群

可以使用 Coherence*Web 在不同 WebLogic 集群之间共享用户会话,不过当您只需在不同 WebLogic 集群中部署的多个应用程序之间共享部分用户会话时,则必须进行一些特定的配置,这是因为 WebLogic 生成的 Cookie 将在两个层面上使用:

  • 前端 Web 层: WebLogic 代理插件读取浏览器提供的 JSESSION Cookie,识别哪个 WebLogic 托管实例拥有会话的主/从副本。
  • Oracle WebLogic/Coherence 层: 当请求到达正确的 Oracle WebLogic 托管实例后,如果已经配置了 Coherence*Web,Oracle Coherence 将重新读取 JSESSION Cookie 以识别用户会话。如果配置了自定义 SessionDistributionController,那么在需要检索会话属性时:
    • 如果存储在本地,则将从 Oracle WebLogic 本地存储中检索
    • 如果存储在共享层,则将从 Oracle Coherence 分布式层中检索

Coherence*Web 如何使用 Oracle WebLogic Cookie

Coherence*Web 依赖 WLS 生成的 Cookie 识别每个用户请求的用户会话。该 Cookie 的工作方式如下:

  1. WLS 发布一个 Cookie,作为对与新会话相关的第一个用户请求的响应。该 Cookie 包含以下信息:

    <SESSION_ID>!<JVM_ID1>!<JVM_ID2>

    其中:
    • <SESSION_ID>:当请求到达 WebLogic 实例后,用于标识用户会话。
    • <JVM_ID1>:前端 Web 服务器中的 WebLogic 代理插件用来标识拥有会话主副本的 WebLogic 实例。
    • <JVM_ID2>:前端 Web 服务器中的 WebLogic 代理插件用来在启用会话故障切换的环境中标识拥有会话从副本的 WebLogic 实例。如果无法连接拥有会话主副本的 WLS 实例,则使用此标识符。(Picasso 项目未启用会话故障切换。)
  2. 在后续请求中,<SESSION_ID> 将保持不变,直到用户会话被抛弃。当任何前述 Oracle WebLogic 实例发生故障或无法连接时,将更新 <JVM_ID1><JVM_ID2>
  3. Cookie 应用于以下两个层面:
    1. Apache Web Server 上安装的代理插件使用 Cookie 中的 JVM 标识符将各用户请求转发给拥有会话主副本的 WLS 实例。
    2. 当请求转发至 Oracle WebLogic 实例后,该实例将使用 Cookie 中存储的会话标识符来标识用户会话。

问题

假如某个请求转发至某个 Oracle WebLogic 集群中的某个实例,后续请求转发至其他 Oracle WebLogic 集群中部署的某个应用程序。

  • 需要共享一些会话属性的应用程序为 JSESSION Cookie 使用相同的 Cookie 名称:
    • Coherence*Web 能够将各请求的会话关联至不同的应用程序。
    • Oracle WebLogic 代理插件将无法识别部署了应用程序的 WebLogic 集群内部的正确托管实例。这是因为每次将新请求重定向至部署在不同集群中的应用程序时,当前 Oracle WebLogic 集群的托管实例的信息将覆盖 Cookie 的部分内容,Cookie 中包含与负责处理请求的 Oracle WebLogic 托管实例的 Java 虚拟机 (JVM) 标识符相关的信息,从而丢失其他 Oracle WebLogic 集群中任何其他托管实例的引用。
    如果使用 sessionDistributionController 将请求发送给部署在不同 WebLogic 集群中的两个不同应用程序,并尝试访问本地存储的属性,那么您将看到这一行为。如果当前实例不是所需实例,那么将无法正确检索本地属性,因为 Oracle WebLogic 代理插件无法访问正确的 Oracle WebLogic 托管实例。
  • 为各应用程序中的 JSESSION Cookie 使用不同的 Cookie 名称:此配置可避免覆盖 JVM 标识符,因为每个应用程序将使用不同的 Cookie,不过每个 Cookie 中的会话标识符也各不相同,因此 Coherence*Web 无法为与单个用户相关的不同应用程序关联会话。

解决方案

在上述场景和要求中,标准 Cookie 机制无法处理部署在两个不同集群中、共享同一个用户会话的两个不同的应用程序;拆分用户会话时,部分会话存储在分布式缓存中,其余会话属性存储在 WLS 实例的本地存储中。

解决此问题的最简单的方法是将整个用户会话存储在分布式缓存中,不过在某些情况下,会话的大小导致这种配置对网络使用情况有很大影响。在这种情况下,无需共享的会话属性的作用域应仍保留在应用程序层。这将避免不同应用程序中的同名会话属性产生冲突。可以利用 Coherence*Web 的会话属性作用域特性实现这一配置。

然而,解决这个问题的最佳途径是为每个需要共享部分会话的应用程序配置不同会话,并确保它们使用相同的会话标识符。为此,可以将共享会话属性的任何应用程序所使用的所有 Cookie 仅置入发送至这些应用程序的第一个用户请求中:

villena-coherence-web-fig02
图 2

示例配置

为此,必须进行以下配置:

  1. 配置各应用程序使用自己的会话 Cookie,即编辑各 Web 应用程序的 weblogic.xml,指定不同的 Cookie 名称:
<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>
  1. 安装一个 J2EE servlet 筛选器,根据需要置入 Cookie:
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>

注:

  • 为确保置入操作涵盖会话的整个生命周期,请勿定义任何失效时间。
  • Cookie 路径必须与 Oracle WebLogic 实例使用的 Cookie 路径匹配。
  • 要为 servlet 筛选器提供与待置入 Cookie 相关的信息,请在每个相关 Oracle WebLogic Server 的启动 JAVA_OPTIONS 中定义两个参数。例如:

 

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;WLS3NID

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

在 session-start-event 过程中,J2EE servlet 筛选器将为其他应用程序(在使用 WLS1SID Cookie 的应用程序上)置入会话 Cookie,反之亦然。这将确保用户在所有相关应用程序都使用相同的会话 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

转发至使用 WLS2SID 或 WLSNSID Cookie 的应用程序的后续请求将更新相应 Cookie 的 JVM 标识符。

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

- Updated cookie by WebLogic Server:

WLSNSID: bhYgZacv61QaJ6Gh2SV9MjtaTu3BdIoE1T98i2rNJB2y4XsypU3r!934311264!1365442637731

总结

本文演示了如何轻松使用 Coherence*Web 实现之前看似不可能的会话共享。

致谢

衷心感谢我的同事 Jonathan Birch 和 Stefano Mantini 为本解决方案的实施提供了最初的想法。

关于作者

Jordi Villena 是 Oracle 的一位 SOA 解决方案架构师,主要负责最新的中间件技术和产品。他对 Oracle WebLogic、Oracle Coherence 和 Oracle Tuxedo 尤为感兴趣,同时也很喜爱 Oracle Service Bus、Oracle WebLogic Portal 和 JRockit 领域的工作。Jordi 从 BEA 转至 Oracle 麾下,最新主要参与一些战略性的 Oracle SOA Suite 和 Oracle ADF 项目。LinkedIn