设为首页 收藏本站
查看: 1835|回复: 0

[经验分享] 【原创】Tomcat集群环境下对session进行外部缓存的方法(1)

[复制链接]

尚未签到

发表于 2015-8-7 10:00:22 | 显示全部楼层 |阅读模式
  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对象等一系列操作:
DSC0000.jpg
  类说明:
  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:



  1 package com.thunisoft.cache.impl.dbimpl;
  2
  3 import java.io.ByteArrayInputStream;
  4 import java.io.ByteArrayOutputStream;
  5 import java.io.IOException;
  6 import java.io.ObjectInputStream;
  7 import java.io.ObjectOutputStream;
  8 import java.sql.Connection;
  9 import java.sql.DriverManager;
10 import java.sql.PreparedStatement;
11 import java.sql.ResultSet;
12 import java.sql.SQLException;
13
14 import org.apache.commons.logging.Log;
15 import org.apache.commons.logging.LogFactory;
16
17 import com.thunisoft.session.CachedSession;
18
19 /**
20  * 数据库缓存Session
21  *
22  * @author zhangxsh
23  *
24  */
25 public class DbCacheSession {
26
27     private static final Log log = LogFactory.getLog(DbCacheSession.class);
28
29     private byte[] writeObject(CachedSession session) {
30         ByteArrayOutputStream byteout = new ByteArrayOutputStream();
31         ObjectOutputStream objout = null;
32         try {
33             objout = new ObjectOutputStream(byteout);
34             session.writeObjectData(objout);
35         } catch (IOException e) {
36             log.error("get bytes from session failed!", e);
37         }
38
39         return byteout.toByteArray();
40     }
41
42     /**
43      * 根据sessionId和Session空对象构造完整的session对象
44      * @param sessionId sessionid
45      * @param session 空session对象
46      * @return 反序列化后的session对象
47      */
48     public CachedSession getSessionObject(String sessionId,
49             CachedSession session) {
50
51         return readObjectFromDb(sessionId, session);
52     }
53
54     /**
55      * 根据sessionId和Session空对象构造完整的session对象
56      * @param sessionId sessionid
57      * @param session 空session对象
58      * @return 反序列化后的session对象
59      */
60     private CachedSession readObjectFromDb(String sessionId,
61             CachedSession session) {
62         PreparedStatement stat = null;
63         byte[] sessionBytes = null;
64         try {
65             stat = getConnection().prepareStatement(
66                     "select c_session from  t_session where c_sid=?");
67             stat.setString(1, sessionId);
68             ResultSet rus = stat.executeQuery();
69             while (rus.next()) {
70                 sessionBytes = rus.getBytes(1);
71             }
72         } catch (SQLException e) {
73             // TODO Auto-generated catch block
74             e.printStackTrace();
75         }
76
77         return readObject(sessionBytes, session);
78     }
79
80     /**
81      * 将Session对象序列化为二进制数据然后保存入库
82      * @param session session对象
83      */
84     public void setSession(CachedSession session) {
85         byte[] sessionBytes = writeObject(session);
86         writeObjectIntoDb(session.getId(), sessionBytes);
87
88     }
89
90     public static void main(String[] args) {
91         // Session s=new Session();
92         // s.setId("AERDS122223");
93         DbCacheSession sess = new DbCacheSession();
94         // sess.setSession(s);
95         String s = "AERDS122223";
96         // System.out.println(sess.getSessionObject(s).getId());
97
98     }
99
100     /**
101      * 将session保存入库,先删再插
102      * @param sessionId sid
103      * @param sessionBytes session对象二进制数据
104      */
105     private void writeObjectIntoDb(String sessionId, byte[] sessionBytes) {
106         PreparedStatement stat = null;
107         Connection con = getConnection();
108         try {
109             stat = con.prepareStatement("delete from t_session where c_sid=?");
110             stat.setString(1, sessionId);
111             stat.execute();
112             stat = con.prepareStatement("insert into t_session values(?,?)");
113             stat.setString(1, sessionId);
114             stat.setBytes(2, sessionBytes);
115             stat.execute();
116         } catch (SQLException e) {
117             e.printStackTrace();
118         } finally {
119             try {
120                 con.close();
121                 stat.close();
122             } catch (SQLException e) {
123                 // TODO Auto-generated catch block
124                 e.printStackTrace();
125             }
126
127         }
128
129     }
130
131     private CachedSession readObject(byte[] sessionBytes, CachedSession session) {
132         if (sessionBytes == null) {
133             return session;
134         }
135         ByteArrayInputStream ins = null;
136         ObjectInputStream objipt = null;
137
138         try {
139             ins = new ByteArrayInputStream(sessionBytes);
140             objipt = new ObjectInputStream(ins);
141             session.readObjectData(objipt);
142             ins.close();
143             objipt.close();
144         } catch (IOException e) {
145             log.error("get session from bytes failed!", e);
146         } catch (ClassNotFoundException e) {
147             log.error("sesializable session failed!", e);
148         }
149         System.out.println(session.getId() + "-session is found");
150         return session;
151
152     }
153
154     protected Connection getConnection() {
155         Connection con = null;
156         try {
157             Class.forName("oracle.jdbc.driver.OracleDriver");
158             con = DriverManager.getConnection(
159                     "jdbc:oracle:thin:@127.0.0.1:1521:ORCL", "zhangxsh",
160                     "zhangxsh");
161
162         } catch (ClassNotFoundException e1) {
163             // TODO Auto-generated catch block
164             e1.printStackTrace();
165         }
166         // Context ctx;
167         // DataSource ds = null;
168         // Connection con = null;
169         // try {
170         // ctx = new InitialContext();
171         // ds = (DataSource) ctx.lookup("jdbc/oracle");
172         // con = ds.getConnection();
173         //
174         // } catch (NamingException e) {
175         // log.error("can not find jndi:" + "jdbc/oracle", e);
176         // } catch (SQLException e) {
177         // // TODO Auto-generated catch block
178         // e.printStackTrace();
179         // }
180         // return con;
181         catch (SQLException e) {
182             // TODO Auto-generated catch block
183             e.printStackTrace();
184         }
185         return con;
186     }
187 }
  
  将该类定义为一个manager加入context.xml中,启动tomcat,不用对应用做任何修改,因为修改的是tomcat。
  

  以Oracle数据库缓存(暂时将session保存到数据库中)为例,部署到Tomcat集群下面(2个节点)测试效果:
  1.首先打开测试页面:
DSC0001.jpg
  可见请求被lb至s1节点服务,sessionid为:
  E4ACDD8588CBCC0BD41B1789E23F1E5F.s1,
  新建会话打开相同链接:
DSC0002.jpg
  发现被lb至s2节点,sessionid为:
  8D1037E94D95E162179921AB7D8CEA80.s2
  查询数据库缓存表:
DSC0003.jpg
  发现这两个会话均被保存至表中。
  下面提交一些信息至session看效果:
DSC0004.jpg
  分别提交了三次,发现都可以正常的读取并显示出来,说明会话可以正确的被修改,并且不会丢失更改。
  同时在会话2也做几次修改session的操作:
DSC0005.jpg
  发现session之间互不影响,是正常的隔离的。
  s1页面的session来自于节点s1,如果关闭s1,会怎么样呢?下面关闭s1节点并刷新s1页面,此时只有节点2存活:
DSC0006.jpg
  发现一样可以正常读取session,该请求被lb至节点2,节点2正常接管服务,并正常的拿到该会话的session信息。
  如果把两个节点都重启呢?发现结果都一样,session信息一样可以读取,如果把数据库中的session删除,刷新页面,session立刻就变了,这就验证了session信息已经完全脱离了中间件了。
  请继续浏览后半部分
  http://www.iyunv.com/zhangxsh/p/3494235.html

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-95077-1-1.html 上篇帖子: 【原创】Tomcat集群环境下对session进行外部缓存的方法(2) 下篇帖子: 让Tomcat支持PHP
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表