文章
面向服务的架构
| |||
|
作者:Jordi Villena
轻松扩展 Coherence*Web 以支持会话共享。
2013 年 10 月
下载
Oracle Coherence
Oracle 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 配置;其要求如下:
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 目前无法解决的:
使用自定义会话分配控制器 (CSDC) 进行优化后,Coherence*Web 可以清理 Oracle WebLogic Server (WLS) 节点中不需要进一步处理特定用户会话请求的会话。使用 Coherence*Web 共享部署在不同 Oracle WebLogic 集群中的多个应用程序的会话属性时,这项优化会清理其他用户请求需要的一些用户会话对象,因此必须禁用此优化。
Oracle 文档指出,使用自定义会话分配控制器时必须将 coherence-sticky-sessions 参数(粘滞会话优化)设为“ture”。
使用 sessionDistributionController 时,此参数会启用一些优化来增强 WLS 节点中的内存管理。因此,当新请求重定向至不同 WLS 会话时,它会清理 WLS 中存储的会话的所有属性。在我们的场景中,由于我们计划接受来自不同 WLS 集群的请求,因此不同 WLS 将针对同一用户会话发出用户请求;这样一来,为了避免在需要之前清理 WLS 节点中的会话,必须禁用此优化(请勿设为“true”)。

如果使用 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 层的不同节点访问对象的副本时,这种事件机制无法确保对象的所有副本是最新的。
可以使用 Coherence*Web 在不同 WebLogic 集群之间共享用户会话,不过当您只需在不同 WebLogic 集群中部署的多个应用程序之间共享部分用户会话时,则必须进行一些特定的配置,这是因为 WebLogic 生成的 Cookie 将在两个层面上使用:
Coherence*Web 依赖 WLS 生成的 Cookie 识别每个用户请求的用户会话。该 Cookie 的工作方式如下:
假如某个请求转发至某个 Oracle WebLogic 集群中的某个实例,后续请求转发至其他 Oracle WebLogic 集群中部署的某个应用程序。
在上述场景和要求中,标准 Cookie 机制无法处理部署在两个不同集群中、共享同一个用户会话的两个不同的应用程序;拆分用户会话时,部分会话存储在分布式缓存中,其余会话属性存储在 WLS 实例的本地存储中。
解决此问题的最简单的方法是将整个用户会话存储在分布式缓存中,不过在某些情况下,会话的大小导致这种配置对网络使用情况有很大影响。在这种情况下,无需共享的会话属性的作用域应仍保留在应用程序层。这将避免不同应用程序中的同名会话属性产生冲突。可以利用 Coherence*Web 的会话属性作用域特性实现这一配置。
然而,解决这个问题的最佳途径是为每个需要共享部分会话的应用程序配置不同会话,并确保它们使用相同的会话标识符。为此,可以将共享会话属性的任何应用程序所使用的所有 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>
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>
注:
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 实现之前看似不可能的会话共享。
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 项目。![]()