BJJC网改版,

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

到这,或许就可以中止了,tomcat集群谁不会建啊?实现了fail-over,当节点1处理会话时如果突然宕掉,那么其他节点会迅速接管而且不停顿的执行服务,对客户端完全透明,apache也很好的执行了lb,虽然还没有进行性能测试,但是起码横向扩展是没有问题的。但是,仔细想想,觉得还是有些问题。

为了实现fail-over,启用了session复制,这每个节点都会留一份session的副本,对于大规模的访问,tomcat能否撑住?假如集群面临1000个并发访问,虽然这1000个请求的压力会分散到3个节点上,但是实际上每个节点都有1000个session数,从资源的消耗上并没有节省多少,这样的话,再大的访问量并不一定撑得住。其实session复制的主要目的就是为了某节点宕掉后其他节点能迅速的接管请求,其实就是一个替补的作用。存在于其他节点中的session其实大部分都是空闲并且高度冗余。也就是说,session复制在节点数增多或者访问量激增时是很耗费资源占用中间件内存的。集群虽然提高了可用性,但是性能没有大的提升,尤其集群节点增多时。每当一个session创建后,节点都要向集群分发session,这样,节点都忙着传播session去了,集群的吞吐量会下降。

所以,一种做法就是将session外部存储或者cache,也就是说,将分散在各个节点中的会话信息拿出来,集中式存储,每个节点接收到客户端的会话请求时都去session池中查找session。这其实并不是什么很时髦的做法,大型的网站很多都采用这种办法,加上前端的页面缓存,提高网站的并发访问量和可用性,只是,在tomcat下如何做?


通过查看tomcat源码,发现可以重写tomcat的会话管理器,自定义类接管该服务,对应session的创建、管理任务进行接管,将session对象从中间件内存中剥离出来进行外部存储。同时对于静态页面或者个性化信息极少的页面,进行页面级cache。

因此,对于外部缓存,我选择的是MemCache,我首先在MemCache上进行了试验。MemCache将是Session和页面的缓存地,不过后来我放弃使用MemCache来缓存Session,原因后面会说明。

首先,我定义了以下类结构来完成这个工作,包括接管Tomcat的session管理以及缓存session对象等一系列操作:

类说明:

CachedSessionManager:该类继承自ManagerBase类,后者为Tomcat的会话管理器。

CachedSession:自定义的Session类,继承自StandardSession,为自定义的一个Tomcat的Session对象。

通过以上两个类,首先将Tomcat的会话管理器架空,其次,对Tomcat处理的Session对象进行了重写,这样,就完全将Session从Tomcat中剥离出来了,Session管理器和被管理的对象都是我自定义的了。

ISessionCaching:接口,抽象了session缓存的各种操作接口,该接口的实现类具体将决定如何对提供的Session进行缓存,我分别实现了四种缓存方案,Map、MemCache、Oracle、TimeSten。

SessionCacheDb:ISessionCaching接口的实现类,提供了数据库缓存session的解决方案,该类继承自DbCacheSession,后者具体决定如何缓存Session至db。

SessionCacheMap:ISessionCaching接口的实现类,提供了JVM内部Map缓存,该方法主要用来测试是否正确的接管了Tomcat的Session管理并能完全的拦截Session对象,无实际意义。

SessionCacheMemCache:ISessionCaching接口的实现类,提供了MemCache缓存Session的解决方案,其中该类依赖于MemCachedManager类,后者具体决定将如何缓存Session至MemCache.

TimeStenCacheSession:ISessionCaching接口的实现类,提供了TimeSten的存储方案,其实该类和SessionCacheDb没有什么区别,就是数据源来源不同。

核心的类:

CachedSessionManager:

package com.thunisoft.session;

import java.io.IOException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap; import org.apache.catalina.Session;
import org.apache.catalina.session.ManagerBase;
import org.apache.catalina.session.StandardSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import com.thunisoft.cache.ISessionCaching; /**
* SessionCacheManager,Session自定义管理器
* @author zhangxsh
*
*/
public class CachedSessionManager extends ManagerBase { private static String cachepath; public String getCachepath() {
return cachepath;
} public void setCachepath(String cachepath) {
this.cachepath = cachepath;
} protected Log log = LogFactory.getLog(CachedSessionManager.class); /**
* 定义如何将Session缓存
*/
private ISessionCaching sessionCache; @Override
public void add(Session session) {
if (log.isDebugEnabled()) {
log.debug("===================" + this.getSessionMaxAliveTime());
}
initCache();
if (session != null) {
sessionCache.addSession(session.getId(), (CachedSession) session,
new Date(getExpireDate()));
}
} /**
* 初始化Cache缓存,通过manager节点配置提供类名加载类
*/
private synchronized void initCache() { if (sessionCache == null) {
try {
sessionCache = (ISessionCaching) Class.forName(cachepath)
.newInstance();
sessionCache.setManager(this);
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} /**
* 获取session超时时间
*
* @return 时间毫秒数
*/
private long getExpireDate() {
return getCurrentTime() + 15 * 60 * 1000;
} private long getCurrentTime() {
return System.currentTimeMillis();
} @Override
public Session createEmptySession() {
System.out.println("createEmptySession");
return new CachedSession(this, sessionCache);
} @Override
public Session createSession() {
if (log.isDebugEnabled()) {
log.debug("createEmptySession:null");
}
return createSession(null);
} @Override
public Session createSession(String sessionId) {
if (log.isDebugEnabled()) {
log.debug(sessionId + "--Session create");
}
Session session = createEmptySession(); session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(this.maxInactiveInterval);
if (sessionId == null) {
sessionId = generateSessionId();
}
session.setId(sessionId);
return (session);
} @Override
public void expireSession(String sessionId) {
initCache();
sessionCache.removeSession(sessionId);
} @Override
public Session findSession(String sessionId) throws IOException {
initCache();
if (sessionId == null) {
return null;
}
return sessionCache.findSession(sessionId);
} @Override
public Session[] findSessions() {
// TODO Auto-generated method stub
return super.findSessions();
} @Override
protected synchronized String generateSessionId() {
String sid = super.generateSessionId();
if (log.isDebugEnabled()) {
log.debug("generateSessionId--" + sid);
}
// TODO Auto-generated method stub
return sid;
} @Override
protected StandardSession getNewSession() {
if (log.isDebugEnabled()) {
log.debug("getNewSession");
}
// TODO Auto-generated method stub
return new CachedSession(this, sessionCache);
} @Override
public HashMap getSession(String sessionId) {
Session s = (Session) sessionCache.getSession(sessionId);
if (s == null) {
if (log.isInfoEnabled()) {
log.info("Session not found " + sessionId);
}
return null;
} Enumeration ee = s.getSession().getAttributeNames();
if (ee == null || !ee.hasMoreElements()) {
return null;
} HashMap map = new HashMap();
while (ee.hasMoreElements()) {
String attrName = (String) ee.nextElement();
map.put(attrName, getSessionAttribute(sessionId, attrName));
} return map;
} @Override
public String getSessionAttribute(String sessionId, String key) {
initCache();
Session s = (Session) sessionCache.getSession(sessionId);
if (s == null) {
if (log.isInfoEnabled())
log.info("Session not found " + sessionId);
return null;
}
Object o = s.getSession().getAttribute(key);
if (o == null)
return null;
return o.toString();
} @Override
public int getSessionMaxAliveTime() {
// TODO Auto-generated method stub
return super.getSessionMaxAliveTime();
} private int sessionAliveTime; public void setSessionAliveTime(int sessionAliveTime) {
// TODO Auto-generated method stub
if (log.isInfoEnabled())
log.info("sessionMaxAliveTime" + sessionMaxAliveTime);
super.setSessionMaxAliveTime(sessionAliveTime);
} @Override
public void remove(Session session) {
if (log.isInfoEnabled())
log.info("removeSession" + session.getId());
sessionCache.removeSession(session.getId());
} @Override
public void setSessionIdLength(int idLength) {
// TODO Auto-generated method stub
super.setSessionIdLength(idLength);
} public int getRejectedSessions() {
// TODO Auto-generated method stub
return 0;
} @Override
public void load() throws ClassNotFoundException, IOException {
// TODO Auto-generated method stub } @Override
public void setRejectedSessions(int arg0) {
// TODO Auto-generated method stub } @Override
public void unload() throws IOException {
// TODO Auto-generated method stub } }

CachedSession:

package com.thunisoft.session;

import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpSession; import org.apache.catalina.Manager;
import org.apache.catalina.SessionListener;
import org.apache.catalina.session.StandardSession; import com.thunisoft.cache.ISessionCaching; public class CachedSession extends StandardSession implements Serializable{ private static final long serialVersionUID = 1L;
/**
* session缓存方法接口
*/
private ISessionCaching sessionCache; Map sessionMap=new ConcurrentHashMap(); /**
* 重写默认构造方法,提供session管理器和缓存接口
* @param manager session管理器
* @param sessionCache session缓存
*/
public CachedSession(Manager manager,ISessionCaching sessionCache){
super(manager);
this.sessionCache=sessionCache;
} @Override
public void expire() {
// TODO Auto-generated method stub
super.expire();
} @Override
public void setAttribute(String arg0, Object arg1, boolean arg2) {
// TODO Auto-generated method stub
/**
* 每当修改了一次对象类型,便重新往manager中set一次
*/
super.setAttribute(arg0, arg1, arg2);
getManager().add(this);
} @Override
public void expire(boolean arg0) {
// TODO Auto-generated method stub
super.expire(arg0);
} @Override
public long getCreationTime() {
// TODO Auto-generated method stub
return super.getCreationTime();
} @Override
public String getId() {
// TODO Auto-generated method stub
return super.getId();
} @Override
public Manager getManager() {
// TODO Auto-generated method stub
return super.getManager();
} @Override
public HttpSession getSession() {
// TODO Auto-generated method stub
return super.getSession();
} @Override
public Object getValue(String name) {
// TODO Auto-generated method stub
return super.getValue(name);
} @Override
public String[] getValueNames() {
// TODO Auto-generated method stub
return super.getValueNames();
} @Override
protected String[] keys() {
// TODO Auto-generated method stub
return super.keys();
} @Override
public void putValue(String name, Object value) {
// TODO Auto-generated method stub
super.putValue(name, value);
} @Override
public void removeAttribute(String name, boolean notify) {
// TODO Auto-generated method stub
super.removeAttribute(name, notify);
} @Override
public void removeAttribute(String name) {
// TODO Auto-generated method stub
super.removeAttribute(name);
} @Override
protected void removeAttributeInternal(String arg0, boolean arg1) {
// TODO Auto-generated method stub
super.removeAttributeInternal(arg0, arg1);
} @Override
public void removeNote(String name) {
// TODO Auto-generated method stub
super.removeNote(name);
} @Override
public void removeSessionListener(SessionListener listener) {
// TODO Auto-generated method stub
super.removeSessionListener(listener);
} @Override
public void removeValue(String name) {
// TODO Auto-generated method stub
super.removeValue(name);
} @Override
public void setAttribute(String arg0, Object arg1) {
// TODO Auto-generated method stub
super.setAttribute(arg0, arg1);
} @Override
public void setId(String id) {
// TODO Auto-generated method stub
super.setId(id);
} @Override
public void setManager(Manager manager) {
// TODO Auto-generated method stub
super.setManager(manager);
} @Override
public String toString() {
// TODO Auto-generated method stub
return "session";
} }

DbCacheSession:

 package com.thunisoft.cache.impl.dbimpl;

 import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import com.thunisoft.session.CachedSession; /**
* 数据库缓存Session
*
* @author zhangxsh
*
*/
public class DbCacheSession { private static final Log log = LogFactory.getLog(DbCacheSession.class); private byte[] writeObject(CachedSession session) {
ByteArrayOutputStream byteout = new ByteArrayOutputStream();
ObjectOutputStream objout = null;
try {
objout = new ObjectOutputStream(byteout);
session.writeObjectData(objout);
} catch (IOException e) {
log.error("get bytes from session failed!", e);
} return byteout.toByteArray();
} /**
* 根据sessionId和Session空对象构造完整的session对象
* @param sessionId sessionid
* @param session 空session对象
* @return 反序列化后的session对象
*/
public CachedSession getSessionObject(String sessionId,
CachedSession session) { return readObjectFromDb(sessionId, session);
} /**
* 根据sessionId和Session空对象构造完整的session对象
* @param sessionId sessionid
* @param session 空session对象
* @return 反序列化后的session对象
*/
private CachedSession readObjectFromDb(String sessionId,
CachedSession session) {
PreparedStatement stat = null;
byte[] sessionBytes = null;
try {
stat = getConnection().prepareStatement(
"select c_session from t_session where c_sid=?");
stat.setString(1, sessionId);
ResultSet rus = stat.executeQuery();
while (rus.next()) {
sessionBytes = rus.getBytes(1);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} return readObject(sessionBytes, session);
} /**
* 将Session对象序列化为二进制数据然后保存入库
* @param session session对象
*/
public void setSession(CachedSession session) {
byte[] sessionBytes = writeObject(session);
writeObjectIntoDb(session.getId(), sessionBytes); } public static void main(String[] args) {
// Session s=new Session();
// s.setId("AERDS122223");
DbCacheSession sess = new DbCacheSession();
// sess.setSession(s);
String s = "AERDS122223";
// System.out.println(sess.getSessionObject(s).getId()); } /**
* 将session保存入库,先删再插
* @param sessionId sid
* @param sessionBytes session对象二进制数据
*/
private void writeObjectIntoDb(String sessionId, byte[] sessionBytes) {
PreparedStatement stat = null;
Connection con = getConnection();
try {
stat = con.prepareStatement("delete from t_session where c_sid=?");
stat.setString(1, sessionId);
stat.execute();
stat = con.prepareStatement("insert into t_session values(?,?)");
stat.setString(1, sessionId);
stat.setBytes(2, sessionBytes);
stat.execute();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
con.close();
stat.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} } } private CachedSession readObject(byte[] sessionBytes, CachedSession session) {
if (sessionBytes == null) {
return session;
}
ByteArrayInputStream ins = null;
ObjectInputStream objipt = null; try {
ins = new ByteArrayInputStream(sessionBytes);
objipt = new ObjectInputStream(ins);
session.readObjectData(objipt);
ins.close();
objipt.close();
} catch (IOException e) {
log.error("get session from bytes failed!", e);
} catch (ClassNotFoundException e) {
log.error("sesializable session failed!", e);
}
System.out.println(session.getId() + "-session is found");
return session; } protected Connection getConnection() {
Connection con = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
con = DriverManager.getConnection(
"jdbc:oracle:thin:@127.0.0.1:1521:ORCL", "zhangxsh",
"zhangxsh"); } catch (ClassNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// Context ctx;
// DataSource ds = null;
// Connection con = null;
// try {
// ctx = new InitialContext();
// ds = (DataSource) ctx.lookup("jdbc/oracle");
// con = ds.getConnection();
//
// } catch (NamingException e) {
// log.error("can not find jndi:" + "jdbc/oracle", e);
// } catch (SQLException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// return con;
catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return con;
}
}

将该类定义为一个manager加入context.xml中,启动tomcat,不用对应用做任何修改,因为修改的是tomcat。


以Oracle数据库缓存(暂时将session保存到数据库中)为例,部署到Tomcat集群下面(2个节点)测试效果:

1.首先打开测试页面:

可见请求被lb至s1节点服务,sessionid为:

E4ACDD8588CBCC0BD41B1789E23F1E5F.s1,

新建会话打开相同链接:

发现被lb至s2节点,sessionid为:

8D1037E94D95E162179921AB7D8CEA80.s2

查询数据库缓存表:

发现这两个会话均被保存至表中。

下面提交一些信息至session看效果:

分别提交了三次,发现都可以正常的读取并显示出来,说明会话可以正确的被修改,并且不会丢失更改。

同时在会话2也做几次修改session的操作:

发现session之间互不影响,是正常的隔离的。

s1页面的session来自于节点s1,如果关闭s1,会怎么样呢?下面关闭s1节点并刷新s1页面,此时只有节点2存活:

发现一样可以正常读取session,该请求被lb至节点2,节点2正常接管服务,并正常的拿到该会话的session信息。

如果把两个节点都重启呢?发现结果都一样,session信息一样可以读取,如果把数据库中的session删除,刷新页面,session立刻就变了,这就验证了session信息已经完全脱离了中间件了。

请继续浏览后半部分

http://www.cnblogs.com/zhangxsh/p/3494235.html

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

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

    Session对象的持久化比较麻烦,虽然有序列化,但是并不确定Session对象中保存的其他信息是否可以序列化,这可能是网上很多解决方案摒弃此种做法的原因,网上的很多做法都是将Session中的att ...

  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. Codeforces Round #308 (Div. 2) A. Vanya and Table 暴力

    A. Vanya and Table Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/552/pr ...

  2. hdu 4828 Grids(拓展欧几里得+卡特兰数)

    题目链接:hdu 4828 Grids 题目大意:略. 解题思路:将上一行看成是入栈,下一行看成是出栈,那么执着的方案就是卡特兰数,用递推的方式求解. #include <cstdio> ...

  3. 图片流Base64编码 转图片

    using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Web; ...

  4. TP函数

    U方法用于完成对URL地址的组装,特点在于可以自动根据当前的URL模式和设置生成对应的URL地址,格式为:U('地址','参数','伪静态','是否跳转','显示域名');在模板中使用U方法而不是固定 ...

  5. alue of type java.lang.String cannot be converted to JSONObject

    /** * 4.0以下系统处理掉返回json的BOM头 * * @param jsonStr * @return */ public static String getJson(String json ...

  6. python 列表函数(转)

    list函数: 功能:将字符创转化为列表,例: 列表基本函数: 1.元素赋值,例: 注意:通过list[0]= 'hel',如果原来位置上有值,会覆盖掉原来的. 2.分片操作 1)显示序列,例: 注意 ...

  7. Parse--Saving Images(翻译)

    原文地址:https://www.parse.com/tutorials/saving-images 学习如何创建一个关于允许用户拍照和上传到parse.com的APP 源码地址:https://gi ...

  8. 关于Android 访问权限设置

    我前几天在做同城交友网(www.niyuewo.com)与医药网(www.yiyaojing.com)时遇到的问题整理如下: Android开发应用程序时,如果应用程序需要访问网络权限,需要在 And ...

  9. hash_map map

    什么时候需要用hash_map,什么时候需要用map? 总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据数据量大小,属于常数级别;而map的查找速度是log(n)级别.并不一定 ...

  10. c++ 设计模式9 (Abstract Factory 抽象工厂模式)

    5.2 抽象工厂模式 动机:在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作:同时,由于需求的变化,往往存在更多系列对象的创建工作. 代码示例: 实现利用数据库的业务逻辑,支持多数据库(Sq ...