博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【原创】Tomcat集群环境下对session进行外部缓存的方法(1)
阅读量:7239 次
发布时间:2019-06-29

本文共 19083 字,大约阅读时间需要 63 分钟。

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。

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

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

SessionCacheMemCacheISessionCaching接口的实现类,提供了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 sid103      * @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 block124                 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 block164             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 block178         // e.printStackTrace();179         // }180         // return con;181         catch (SQLException e) {182             // TODO Auto-generated catch block183             e.printStackTrace();184         }185         return con;186     }187 }

 

将该类定义为一个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信息已经完全脱离了中间件了。

请继续浏览后半部分

转载于:https://www.cnblogs.com/zhangxsh/p/3494165.html

你可能感兴趣的文章
Objective-C 资源收藏
查看>>
MFC——从实现角度分析微云界面
查看>>
正则 群组 Group
查看>>
An Introduction To The SQLite C/C++ Interface
查看>>
关闭Pycharm拼写检查
查看>>
一个优秀的程序员是如何炼成的?
查看>>
区块链的12个技术理解误区,你知道哪些?
查看>>
常用面试编程训练5大网站!
查看>>
对比三大旗舰真正全面屏,荣耀Magic2不学OV,绝不妥协
查看>>
SMILEY黄色笑脸攻占华为授权体验店 网友惊呼“太魔性”!
查看>>
陈林接替张一鸣任今日头条CEO 字节跳动学谷歌进行架构升级
查看>>
万盏彩灯迎新春 江苏大丰梅花湾新春灯会小年夜亮灯
查看>>
33名中国游客护照被偷 驻意大利使馆加班办证获赞
查看>>
天津一商场还原80、90年代家庭过年场景
查看>>
「每天一道面试题」说一下Spring框架中Bean的生存周期
查看>>
云南加快发展乡村旅游 摆脱“美丽的贫困”
查看>>
五大联赛半程战罢:三卫冕冠军强势 两队欲颠覆王权
查看>>
银川机场4万张机场巴士免票送高校学生
查看>>
高校获批数据科学与大数据技术专业,《云计算》教材怎么选?
查看>>
前端面试查漏补缺--Index篇(12万字符合集)
查看>>