Session对象的持久化比较麻烦,虽然有序列化,但是并不确定Session对象中保存的其他信息是否可以序列化,这可能是网上很多解决方案摒弃此种做法的原因,网上的很多做法都是将Session中的attribute信息持久化并结构化存储,这显然很方便,但是session中的其他信息就丢了,否则仍然占据中间件内存,通过查看源码,惊喜的发现Tomcat对象提供了Session序列化的接口以及相关实现(Store),不过不是很满足需求,对其进行了一些改造就ok了,最终,Session对象作为一个整体,以二进制的形式保存在blob中,当反序列化时,还要装配Session和SessionManager之间的依赖关系。主要用到了session的以下方法,主要思想就是将session化整为零,传入一个输入流,将属性都写入该流中:

  protected void writeObject(ObjectOutputStream stream) throws IOException {

         // Write the scalar instance variables (except Manager)
stream.writeObject(new Long(creationTime));
stream.writeObject(new Long(lastAccessedTime));
stream.writeObject(new Integer(maxInactiveInterval));
stream.writeObject(new Boolean(isNew));
stream.writeObject(new Boolean(isValid));
stream.writeObject(new Long(thisAccessedTime));
stream.writeObject(id);
if (manager.getContainer().getLogger().isDebugEnabled())
manager.getContainer().getLogger().debug
("writeObject() storing session " + id); // Accumulate the names of serializable and non-serializable attributes
String keys[] = keys();
ArrayList saveNames = new ArrayList();
ArrayList saveValues = new ArrayList();
for (int i = 0; i < keys.length; i++) {
Object value = attributes.get(keys[i]);
if (value == null)
continue;
else if ( (value instanceof Serializable)
&& (!exclude(keys[i]) )) {
saveNames.add(keys[i]);
saveValues.add(value);
} else {
removeAttributeInternal(keys[i], true);
}
} // Serialize the attribute count and the Serializable attributes
int n = saveNames.size();
stream.writeObject(new Integer(n));
for (int i = 0; i < n; i++) {
stream.writeObject((String) saveNames.get(i));
try {
stream.writeObject(saveValues.get(i));
if (manager.getContainer().getLogger().isDebugEnabled())
manager.getContainer().getLogger().debug
(" storing attribute '" + saveNames.get(i) +
"' with value '" + saveValues.get(i) + "'");
} catch (NotSerializableException e) {
manager.getContainer().getLogger().warn
(sm.getString("standardSession.notSerializable",
saveNames.get(i), id), e);
stream.writeObject(NOT_SERIALIZED);
if (manager.getContainer().getLogger().isDebugEnabled())
manager.getContainer().getLogger().debug
(" storing attribute '" + saveNames.get(i) +
"' with value NOT_SERIALIZED");
}
} }

这样将这个流的数据转换为一个字节流保存为blob即可。还原时,同样使用以下方法还原,将该流传入,方法会返回一个离线的Session对象,什么叫离散的?就是没有和具体的SessionMananger关联的:

 protected void readObject(ObjectInputStream stream)
throws ClassNotFoundException, IOException { // Deserialize the scalar instance variables (except Manager)
authType = null; // Transient only
creationTime = ((Long) stream.readObject()).longValue();
lastAccessedTime = ((Long) stream.readObject()).longValue();
maxInactiveInterval = ((Integer) stream.readObject()).intValue();
isNew = ((Boolean) stream.readObject()).booleanValue();
isValid = ((Boolean) stream.readObject()).booleanValue();
thisAccessedTime = ((Long) stream.readObject()).longValue();
principal = null; // Transient only
// setId((String) stream.readObject());
id = (String) stream.readObject();
if (manager.getContainer().getLogger().isDebugEnabled())
manager.getContainer().getLogger().debug
("readObject() loading session " + id); // Deserialize the attribute count and attribute values
if (attributes == null)
attributes = new Hashtable();
int n = ((Integer) stream.readObject()).intValue();
boolean isValidSave = isValid;
isValid = true;
for (int i = 0; i < n; i++) {
String name = (String) stream.readObject();
Object value = (Object) stream.readObject();
if ((value instanceof String) && (value.equals(NOT_SERIALIZED)))
continue;
if (manager.getContainer().getLogger().isDebugEnabled())
manager.getContainer().getLogger().debug(" loading attribute '" + name +
"' with value '" + value + "'");
attributes.put(name, value);
}
isValid = isValidSave; if (listeners == null) {
listeners = new ArrayList();
} if (notes == null) {
notes = new Hashtable();
}
}


说说我为什么实现以上三种缓存方案。

最开始我通过Map缓存(SessionCacheMap)测试通过之后,立刻将其迁移到MemCache中,因为Map缓存还是属于JVM进程内缓存,Session仍然在中间件内部,只是保存在我自定义的一块内存区域中而已,只是验证是否可以完全的拦截,因此没有实际意义,需要将其从JVM内部拿出来。因此我迁移至MemCache中去了。

而为什么最后没有使用MemCache,MemCache没有保障,这个缓存性能很高,功能强大,但是它并不对缓存信息持久化,一旦它宕掉,session信息就都丢了,如果一些页面缓存丢了倒也没什么,重启再建立就好了,但是如果session信息丢了,那就比较严重了,而且这个缓存没有持久化方案,所以,最后我没有采用它缓存session,而是使用它来缓存页面。

所以我选择了dbms来存储session信息,dbms强大、可靠的数据管理保证session不会丢失,每个session都被持久化进数据库中,上个例子我们可以看到,我们把所有节点都关闭再启动,客户端刷新界面,仍然可以正常显示,session不会丢失。

可靠性有了保证,获取session是一个非常频繁的操作,如何保证性能呢?每次都去查询数据库,性能还是没有保证,这地方很容易成为瓶颈,尤其大规模访问时,虽然oracle可以将记录保存在buffercache中,但是毕竟它不是专业的缓存, bufferCache大小有限,而且每次插入删除session信息会涉及到磁盘的读写,这都是瓶颈,所以,我又引入了timesten内存数据库,它在Oracle的前端作为Oracle的一个超大的bufferCache,session持久化信息首先进入timesten,而timesten后台异步将session持久化至oracle中, TimeSten其实就是一个大的前端缓存而已,进一步讲,就是一个外置的bufferCache,后端的Oracle保证TimeSten万一宕掉,持久化的session数据不会丢失,宕掉就宕掉了,重启就ok了。所以,我在TimeSten11g版本下搭建了环境,建立了一个AWT类型的CacheGroup这个Cache就缓存了一张表名为T_SESSION:这张表的定义为:

 create table T_SESSION
(
C_SID VARCHAR2(200) primary key ,
C_SESSION BLOB
)

基于这张表的CacheGroup我定义如下(oracle的blob在timesten中影射为varbinary):

create dynamic asynchronous writethrough cache group g_awt from uss.t_session ( c_sid varchar(200) not null , c_session varbinary(262144),primary key(c_sid));

该Group采用异步方式写入oracle,数据的插入、删除都在timesten中完成,而timesten会异步的将数据刷新至oracle,看下例子:

首先在tt中插入1条数据:

再看下oracle中:

Timesten中删掉一条记录:

再看oracle中:

发现数据已经被异步更新。因此,采用timesten缓存session,oracle持久化session的基础环境已经OK了。下面其实就是将原本往oracle中保存的session往timesten中保存即可,其实就是切换一下Connection来源即可,此处省略。


那么如何处理session失效的问题?tomcat的session有一个lifescycle的概念,但是现在我把session对象从tomcat中完全剥离出来了,这样便不能由tomcat来维护了,怎么办呢?其实很简单,方案有两种:

1.我在t_session表的后面增加了d_create一列,类型为timestamp,该列代表这个session对象的创建时间戳,当session创建时记录时间戳,当session被修改时,更新这个时间戳,我定义一个job,这个job每5秒钟扫描一遍t_session记录,将截至到现在d_create超过某值的记录删除:

Delete from t_session where sysdate-c_create>n;这个n就是session的失效时间。

2.如果使用内存数据库,那更简单了,在文档中发现timesten有aging的概念,什么叫aging?看下面的CacheGroup定义:

create dynamic asynchronous writethrough cache group g_awt from uss.t_session ( c_sid varchar(200) not null , c_session varbinary(262144),d_create timestamp,primary key(c_sid)) AGING USE  d_create LIFETIME 15 minutes CYCLE 5 seconds ON;

aging子句代表每隔6秒钟检测一次记录,使d_create超过15分钟的记录失效,是不是很方便?交给timesten去维护就ok了。


再说说我是如何实现页面缓存的,页面缓存使用Filter来实现,包装了ResponseWrapper类,该类拦截response的信息,将中间件的响应数据拦截并保存至MemCache中,当下次同样的URL到来,只需要去MemCache中取就可以了。

PageCacheFilter用于拦截url,将指定规则的url拦截响应内容并缓存至MemCache,其中具体的是由MonitorResponseWrapper类来完成的,中间件的响应数据存储在它的ByteArray输出流中,它继承自HttpServletResponseWrapper类,典型的装饰模式的应用。

package com.thunisoft.filter;

import java.io.IOException;
import java.util.Date; 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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; public class PageCacheFilter implements Filter { public void destroy() {
// TODO Auto-generated method stub } public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
MonitorResponseWrapper response = new MonitorResponseWrapper(
(HttpServletResponse) resp);
String url = ((HttpServletRequest) req).getRequestURI();
// MemCachedManager.getInstance().delete(url);
Object o = MemCachedManager.getInstance().get(url);
Date d = new Date(System.currentTimeMillis() + 10000);
if (o == null) {
chain.doFilter(req, response);
byte b[] = response.getResponseData();
resp.getOutputStream().write(b);
MemCachedManager.getInstance().set(url, b, d);
} else {
byte[] b = (byte[]) o;
resp.getOutputStream().write(b);
} } public static void main(String[] args) {
Date d = new Date(500);
} public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub } }
package com.thunisoft.filter;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper; public class MonitorResponseWrapper extends HttpServletResponseWrapper { private ByteArrayOutputStream buffer = null;
private ServletOutputStream out = null;
private PrintWriter writer = null; public MonitorResponseWrapper(HttpServletResponse resp) throws IOException {
super(resp);
buffer = new ByteArrayOutputStream();// 真正存储数据的流
out = new WapperedOutputStream(buffer);
writer = new PrintWriter(new OutputStreamWriter(buffer, this
.getCharacterEncoding()));
} // 重载父类获取outputstream的方法
@Override
public ServletOutputStream getOutputStream() throws IOException {
return out;
} // 重载父类获取writer的方法
@Override
public PrintWriter getWriter() throws UnsupportedEncodingException {
return writer;
} // 重载父类获取flushBuffer的方法
@Override
public void flushBuffer() throws IOException {
if (out != null) {
out.flush();
}
if (writer != null) {
writer.flush();
}
} static int i; public static void main(String[] args) { System.out.println(i);
} @Override
public void reset() {
buffer.reset();
} public byte[] getResponseData() throws IOException {
// 将out、writer中的数据强制输出到WapperedResponse的buffer里面,否则取不到数据
flushBuffer();
return buffer.toByteArray();
} // 内部类,对ServletOutputStream进行包装
private class WapperedOutputStream extends ServletOutputStream {
private ByteArrayOutputStream bos = null; public WapperedOutputStream(ByteArrayOutputStream stream)
throws IOException {
bos = stream;
} @Override
public void write(int b) throws IOException {
bos.write(b);
}
}
}
package com.thunisoft.filter;

import java.util.Date;

import org.apache.catalina.Session;

import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;
import com.thunisoft.session.CachedSession; /**
*
* Memcached 缓存管理
*
* @author Administrator
*
*
*/
public class MemCachedManager {
// 创建全局的唯一实例
protected static MemCachedClient mcc = new MemCachedClient();
protected static MemCachedManager memCachedManager = new MemCachedManager();
// 设置与缓存服务器的连接池
static {
// 服务器列表和其权重
String[] servers = { "172.20.70.251:12000" };
Integer[] weights = { 3 };
// 获取socke连接池的实例对象
SockIOPool pool = SockIOPool.getInstance();
// 设置服务器信息
pool.setServers(servers);
pool.setWeights(weights);
// 设置初始连接数、最小和最大连接数以及最大处理时间
pool.setInitConn(5);
pool.setMinConn(5);
pool.setMaxConn(250);
pool.setMaxIdle(1000 * 60 * 60 * 6);
// 设置主线程的睡眠时间
pool.setMaintSleep(30);
// 设置TCP的参数,连接超时等
pool.setNagle(false);
pool.setSocketTO(3000);
pool.setSocketConnectTO(0);
// 初始化连接池
pool.initialize();
// 压缩设置,超过指定大小(单位为K)的数据都会被压缩
mcc.setCompressEnable(true);
mcc.setCompressThreshold(64 * 1024);
} /**
*
* 保护型构造方法,不允许实例化!
*
*
*/
protected MemCachedManager() {
} /**
*
* 获取唯一实例.
*
*
*
* @return
*/
public synchronized static MemCachedManager getInstance() {
return memCachedManager;
} /**
*
* 添加一个指定的值到缓存中.
*
*
*
* @param key
*
* @param value
*
* @return
*/
public boolean add(String key, Object value) {
return mcc.add(key, value);
} public boolean add(String key, Object value, Date expiry) {
return mcc.add(key, value, expiry);
} public boolean set(String key, Object value, Date expiry) { return mcc.set(key, value, expiry); }
public boolean set(String key, Object value) { return mcc.set(key, value); } /**
*
* 更新缓存对象
*
* @param key
*
* @param value
*
* @return
*/
public boolean replace(String key, Object value) {
return mcc.replace(key, value);
} public boolean replace(String key, Object value, Date expiry) {
return mcc.replace(key, value, expiry);
} /**
*
* 根据指定的关键字获取对象.
*
*
*
* @param key
*
* @return
*/
public Object get(String key) {
return mcc.get(key);
}
public boolean delete(String key){
return mcc.delete(key);
} public static void main(String[] args) { MemCachedManager cache = MemCachedManager.getInstance();
Session s=new CachedSession(null,null);
cache.set("111",s, new Date(System.currentTimeMillis()+10*1000));
// Object o=cache.get("E4A8127405876F0F94627BB0F440CCDC"); System.out.println("get value : " + cache.get("111"));
}
}

package com.thunisoft.filter;

import java.util.Date;

import org.apache.catalina.Session;

import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;
import com.thunisoft.session.CachedSession; /**
*
* Memcached 缓存管理
*
* @author Administrator
*
*
*/
public class MemCachedManager {
// 创建全局的唯一实例
protected static MemCachedClient mcc = new MemCachedClient();
protected static MemCachedManager memCachedManager = new MemCachedManager();
// 设置与缓存服务器的连接池
static {
// 服务器列表和其权重
String[] servers = { "172.20.70.251:12000" };
Integer[] weights = { 3 };
// 获取socke连接池的实例对象
SockIOPool pool = SockIOPool.getInstance();
// 设置服务器信息
pool.setServers(servers);
pool.setWeights(weights);
// 设置初始连接数、最小和最大连接数以及最大处理时间
pool.setInitConn(5);
pool.setMinConn(5);
pool.setMaxConn(250);
pool.setMaxIdle(1000 * 60 * 60 * 6);
// 设置主线程的睡眠时间
pool.setMaintSleep(30);
// 设置TCP的参数,连接超时等
pool.setNagle(false);
pool.setSocketTO(3000);
pool.setSocketConnectTO(0);
// 初始化连接池
pool.initialize();
// 压缩设置,超过指定大小(单位为K)的数据都会被压缩
mcc.setCompressEnable(true);
mcc.setCompressThreshold(64 * 1024);
} /**
*
* 保护型构造方法,不允许实例化!
*
*
*/
protected MemCachedManager() {
} /**
*
* 获取唯一实例.
*
*
*
* @return
*/
public synchronized static MemCachedManager getInstance() {
return memCachedManager;
} /**
*
* 添加一个指定的值到缓存中.
*
*
*
* @param key
*
* @param value
*
* @return
*/
public boolean add(String key, Object value) {
return mcc.add(key, value);
} public boolean add(String key, Object value, Date expiry) {
return mcc.add(key, value, expiry);
} public boolean set(String key, Object value, Date expiry) { return mcc.set(key, value, expiry); }
public boolean set(String key, Object value) { return mcc.set(key, value); } /**
*
* 更新缓存对象
*
* @param key
*
* @param value
*
* @return
*/
public boolean replace(String key, Object value) {
return mcc.replace(key, value);
} public boolean replace(String key, Object value, Date expiry) {
return mcc.replace(key, value, expiry);
} /**
*
* 根据指定的关键字获取对象.
*
*
*
* @param key
*
* @return
*/
public Object get(String key) {
return mcc.get(key);
}
public boolean delete(String key){
return mcc.delete(key);
} public static void main(String[] args) { MemCachedManager cache = MemCachedManager.getInstance();
Session s=new CachedSession(null,null);
cache.set("111",s, new Date(System.currentTimeMillis()+10*1000));
// Object o=cache.get("E4A8127405876F0F94627BB0F440CCDC"); System.out.println("get value : " + cache.get("111"));
}
}

所以,最终,这个方案的整体架构如下:

这个架构的优点有以下:

使用廉价的Tomcat集群,摒弃了效率低下的session复制,可以实现大规模的横向扩展,增加网站的吞吐量和并发访问量,如果网站的并发访问超大,那么可以对前端的Apache进行分层的扩展,也很简单,未来Session如果更多的话,那Timsten的查询可能会成为瓶颈,那可以继续将TimeSten拆分,在tomcat的会话管理器中对Session查询进行路由,比如对sessionid进行hash散列,将session分散缓存至分布式的TimeSten中,客户端请求时,根据hash映射确定sessionid存在于哪个TimeSten节点中,达到分散压力的目的,这只是对于一般网站,如果对于mis系统,可能最终的压力都跑到数据库上而非中间件了,那又是另外一回事了。

【原创】Tomcat集群环境下对session进行外部缓存的方法(2)的更多相关文章

  1. 【原创】Tomcat集群环境下对session进行外部缓存的方法(1)

    BJJC网改版, 计划将应用部署在tomcat集群上,集群的部署方案为Apache+Tomcat6,连接件为mod_jk,其中开启了session复制和粘性session.计划节点数为3个. 到这,或 ...

  2. 集群环境下,Session管理的几种手段

    集群环境下,Session管理的几种手段 1.Session复制 缺点:集群服务器间需要大量的通信进行Session复制,占用服务器和网络的大量资源. 由于所有用户的Session信息在每台服务器上都 ...

  3. 在tomcat集群环境下redis实现分布式锁

    上篇介绍了redis在集群环境下如何解决session共享的问题.今天来讲一下如何解决分布式锁的问题 什么是分布式锁? 分布式锁就是在多个服务器中,都来争夺某一资源.这时候我们肯定需要一把锁是不是 , ...

  4. 集群环境下的Session管理

    1. 集群环境下的管理HTTPSSession所遇到的问题 一台服务器对应这个一个session对象,无法在另外一个服务器互通 解决方法: 1. Session 的 Replication(复制)将当 ...

  5. weblogic 12C集群环境下的session复制

    做过weblogic集群环境的人应该都清楚,要想实现session同步,必须满足两个条件:第一,在weblogic.xml里面增加session同步相关的代码:第二,所有放入session的类都要序列 ...

  6. Tomcat集群环境下session共享方案 通过memcached 方法实现

    对于web应用集群的技术实现而言,最大的难点就是:如何能在集群中的多个节点之间保持数据的一致性,会话(Session)信息是这些数据中最重要的一块.要实现这一点, 大体上有两种方式:一种是把所有Ses ...

  7. 集群环境下Shiro Session的管理

    问题引入 紧接上篇连接 在多台tomcat集群中,shiro管理的session需要放在Redis中,我们只需要增加redisSessionDAO的配置就行 <!-- 定义会话管理器的操作 表示 ...

  8. 集群环境下的Session共享

    一.Cookie机制和Session机制回顾 1)定义:Session成为“会话”,具体是指一个终端用户与交互系统进行通信的时间间隔,通常指从注册进入系统到注销退出系统之间所经过的时间.Session ...

  9. redis内存分配管理与集群环境下Session管理

    ##################内存管理############### 1.Redis的内存管理 .与memcache不同,没有实现自己的内存池 .在2..4以前,默认使用标准的内存分配函数(li ...

随机推荐

  1. 【JavaScript】关于delete

    delete 只能删除属性,不能删除变量 比如 var m = "haha"; delete m; //false m = "haha";----->wi ...

  2. iOS CocoaPods安装和使用图解

    Cocoapods安装步骤 1.升级Ruby环境 sudo gem update --system 如果Ruby没有安装,请参考 如何在Mac OS X上安装 Ruby运行环境 2.安装CocoaPo ...

  3. [Java] 识别图片验证码

    现在大多数网站都采用了验证码来防止暴力破解或恶意提交.但验证码真的就很安全吗?真的就不能被机器识别?? 我先讲讲我是怎么实现站外提交留言到一个网站的程序. 这个网站的留言版大致如下: 我一看这种简单的 ...

  4. 关于egg的压缩测试报告

     167274doc  单字分词 全压缩 时间  real 15m58.464suser 13m52.157ssys 2m3.445s   空间 tmpfs 12G 1.5G 11G 13% /ape ...

  5. 主流手持设备GPU性能比较

    设备 GPU CPU 每秒像素填充率 每秒三角形生成 内存 iPhone4 PowerVR SGX 535 ARM Cortex-A8 800M     512M iPod touch 4 Power ...

  6. 进程控制之fork函数

    一个现有进程可以调用fork函数创建一个新进程. #include <unistd.h> pid_t fork( void ); 返回值:子进程中返回0,父进程中返回子进程ID,出错返回- ...

  7. 纯css3实现的动画加载条

    之前大大家分享了很多款加载条.今天给大家带来一款纯css3实现的动画加载条. 这款加载条适用浏览器:360.FireFox.Chrome.Safari.Opera.傲游.搜狗.世界之窗. 不支持IE8 ...

  8. 转:《JavaScript—之对象参数的引用传递》

    转自:博客园 Wayou http://www.cnblogs.com/Wayou/p/javascript_arguments_passing_with_reference.html 变量 1.Ja ...

  9. android 搭建开发环境

    法一.直接集成的ADT+ECLIPSE 还有64位的jdk即可 法二.EClipse装好后,下载好SDK,ADT(在线或离线装) sdk下载后,点sdk.exe 文件. 如果更新失败,解决方法如下 C ...

  10. Java再学习——synchronized与volatile

    volatile:只保证共享资源的可见性的,任何修改都写在主存,所有线程马上就能看到,适用于新值不依赖于旧值的情形. synchronized:保证可操作的原子性一致性和可见性. volatile和s ...