1缓存机制

Tomcat默认将Session保存到内存中。但同时,Tomcat也提供了PersistentManager配合不同的Store实现的方式,使Session可以被保存到不同地方(Database,Redis,Memcached等)。

例如下面的配置:

         <ManagerclassName="org.apache.catalina.session.PersistentManager"
debug="0"
saveOnRestart="true"
maxActiveSession="-1"
minIdleSwap="0"
maxIdleSwap="0"
maxIdleBackup="-1">
<StoreclassName="com.cdai.test.RedisStore" host="192.168.1.1"port="6379"/>
</Manager>

通过PersistentManager配合RedisStore,实现了Session存储到Redis中。但要注意的是:切换Manager和Store实现都不会让Session全部保存到其他位置。

Tomcat只是在下面三种情况会将Session通过Store保存起来:

Ø  当Session的空闲时间超过minIdleSwap和maxIdleSwap时,会将Session换出

Ø  当Session的空闲时间超过maxIdleBackup时,会将Session备份出去

Ø  当Session总数大于maxActiveSession时,会将超出部分的空闲Session换出

所以只是简单地切换Manager和Store的实现,并不能使Tomcat把Session都保存到我们想要的地方。Tomcat还是会把内存当做Session的主存储器,我们配置的Store只是作为辅助存储器。 下面先来深入研究下Tomcat源码来研究下Session管理机制,最后我们再看下如何能达到我们想要的效果。

2深入源码

2.1使用Session

下面以一个使用Session的Servlet为切入点,开始分析Tomcat源码。

public class LoginServletextends HttpServlet {

    private static int i = 0;

    @Override
protected void service(HttpServletRequestreq, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session =req.getSession(true);
System.out.println(session.getAttribute("cdai"));
session.setAttribute("cdai","helloworld" + i++);
} }

默认配置下,我们通过HttpServletRequest得到的HttpSession实现是StandardSession。设置属性时,只是把属性值保存到StandardSession的一个Map中。

2.2后台线程

在Tomcat 6中所有后台线程的调度都是在ContainerBase.backgroundProcess()中统一处理的。

    public void backgroundProcess() {

        if (!started)
return; if (cluster != null) {
try {
cluster.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.cluster",cluster), e);
}
}
if (loader != null) {
try {
loader.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.loader",loader), e);
}
}
if (manager != null) {
try {
manager.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.manager",manager), e);
}
}
if (realm != null) {
try {
realm.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.realm",realm), e);
}
}
Valve current = pipeline.getFirst();
while (current != null) {
try {
current.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.valve",current), e);
}
current = current.getNext();
}
lifecycle.fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}

上面源码中我们关心的是manager.backgroundProcess()一行,这是Manager后台处理的入口。

先来看下Manager的类关系:

ManagerBase 实现了Manager接口的backgroundProcess()方法。在ManagerBase.backgroundProcess()中调用子类PersistentManagerBase.processExpire()。在processExpire()中会对前面提到的Session被持久化的三种情况进行处理。下面就来看下这三种情况的源码。

2.3换出和备份

先以情况1换出空闲时间过长的Session的源码为例。

    protected void processMaxIdleSwaps() {
if (!isStarted() || maxIdleSwap < 0)
return; Session sessions[] = findSessions();
long timeNow =System.currentTimeMillis(); // Swap out all sessions idle longerthan maxIdleSwap
if (maxIdleSwap >= 0) {
for (int i = 0; i <sessions.length; i++) {
StandardSession session =(StandardSession) sessions[i];
synchronized (session) {
if (!session.isValid())
continue;
int timeIdle = // Truncate,do not round up
(int) ((timeNow -session.getLastAccessedTime()) / 1000L);
if (timeIdle > maxIdleSwap && timeIdle> minIdleSwap) {
if (session.accessCount!= null &&
session.accessCount.get() > 0) {
// Session iscurrently being accessed - skip it
continue;
}
if(log.isDebugEnabled())
log.debug(sm.getString
("persistentManager.swapMaxIdle",
session.getIdInternal(), newInteger(timeIdle)));
try {
swapOut(session);
} catch (IOException e){
; // This is logged in writeSession()
}
}
}
}
} } protected void swapOut(Session session)throws IOException {
if (store == null ||!session.isValid()) {
return;
} ((StandardSession)session).passivate();
writeSession(session);
super.remove(session);
session.recycle();
} protected void writeSession(Sessionsession) throws IOException {
if (store == null || !session.isValid()){
return;
} try {
if(SecurityUtil.isPackageProtectionEnabled()){
try{
AccessController.doPrivileged(newPrivilegedStoreSave(session));
}catch(PrivilegedActionExceptionex){
Exception exception =ex.getException();
log.error("Exceptionin the Store during writeSession: "
+ exception);
exception.printStackTrace();
}
} else {
store.save(session);
}
} catch (IOException e) {
log.error(sm.getString
("persistentManager.serializeError",session.getIdInternal(), e));
throw e;
}
} private class PrivilegedStoreSave implementsPrivilegedExceptionAction {
private Session session; PrivilegedStoreSave(Session session){
this.session = session;
} public Object run() throws Exception{
store.save(session);
return null;
}
}

processMaxIdleSwaps()会将每个Session的空闲时间与minIdleSwap和maxIdleSwap比较,然后调用swapOut()将Session换出。在swapOut()中,通过PrivilegedStoreSave类调用store的save()方法将session保存到不同的地方。

另外两种情况的处理与processMaxIdleSwaps()类似。处理方法为:processMaxIdleBackups()和processMaxActiveSwaps()。

3切换方案

3.1简单方案

一种简单方法是依旧使用Tomcat提供的PersistentManager,自己实现Store类。之后通过调整Manager参数让Tomcat尽快把Session换出,例如开篇那段配置中,将min/maxIdleSwap设置为0。这种方法的缺点是Servlet设置Session属性,并且请求结束后,可能很大延迟后Session才会被换出。

3.2最终方案

下面来看下开源tomcat-redis-session-manager实现的源码,分析一下如何能完美切换。

这是tomcat-redis-session-manager官方提供的配置:

<ValveclassName="com.radiadesign.catalina.session.RedisSessionHandlerValve"/>

<ManagerclassName="com.radiadesign.catalina.session.RedisSessionManager"

host="localhost" <!-- optional: defaults to"localhost" -->

port="6379" <!-- optional: defaults to "6379"-->

database="0" <!-- optional: defaults to "0"-->

maxInactiveInterval="60" <!-- optional: defaults to"60" (in seconds) --> />

可以看到除了自定义了Manager,它还提供了一个Valve实现。在Tomcat中,请求被Servlet处理前是要经过管道中的许多Valve对象处理的,类似于Struts2中的Interceptor。

public classRedisSessionHandlerValve extends ValveBase {
private final Log log =LogFactory.getLog(RedisSessionManager.class);
private RedisSessionManager manager; public voidsetRedisSessionManager(RedisSessionManager manager) {
this.manager = manager;
} @Override
public void invoke(Request request, Responseresponse) throws IOException, ServletException {
try {
getNext().invoke(request, response);
} finally {
final Session session =request.getSessionInternal(false);
storeOrRemoveSession(session);
manager.afterRequest();
}
} private void storeOrRemoveSession(Sessionsession) {
try {
if (session != null) {
if (session.isValid()) {
log.trace("Request with sessioncompleted, saving session " + session.getId());
if (session.getSession() != null) {
log.trace("HTTP Sessionpresent, saving " + session.getId());
manager.save(session);
} else {
log.trace("No HTTP Sessionpresent, Not saving " + session.getId());
}
} else {
log.trace("HTTP Session has beeninvalidated, removing :" + session.getId());
manager.remove(session);
}
}
} catch (Exception e) {
// Do nothing.
}
}
}

RedisSessionHandlerValve的处理逻辑很简单:调用getNext().invoke(request,response)使后续Valve继续处理请求。在storeOrRemoveSession()中直接调用manager.save(),而不是像之前等待Tomcat调度。

RedisSessionManager.save()实现也很简单,将Session序列化后使用Jedis保存到Redis中。

  public void save(Session session) throwsIOException {
Jedis jedis = null;
Boolean error = true; try {
log.trace("Saving session " +session + " into Redis"); RedisSession redisSession =(RedisSession) session; if (log.isTraceEnabled()) {
log.trace("Session Contents[" + redisSession.getId() + "]:");
Enumeration en =redisSession.getAttributeNames();
while(en.hasMoreElements()) {
log.trace(" " + en.nextElement());
}
} Boolean sessionIsDirty =redisSession.isDirty(); redisSession.resetDirtyTracking();
byte[] binaryId =redisSession.getId().getBytes(); jedis = acquireConnection(); Boolean isCurrentSessionPersisted =this.currentSessionIsPersisted.get();
if (sessionIsDirty ||(isCurrentSessionPersisted == null || !isCurrentSessionPersisted)) {
jedis.set(binaryId, serializer.serializeFrom(redisSession));
} currentSessionIsPersisted.set(true); log.trace("Setting expire timeout onsession [" + redisSession.getId() + "] to " +getMaxInactiveInterval());
jedis.expire(binaryId,getMaxInactiveInterval()); error = false;
} catch (IOException e) {
log.error(e.getMessage()); throw e;
} finally {
if (jedis != null) {
returnConnection(jedis, error);
}
}
}

参考资料

1 tomcat-redis-session-manager官网

https://github.com/jcoleman/tomcat-redis-session-manager

2《深入剖析Tomcat》

深入剖析Tomcat会话机制的更多相关文章

  1. 深入剖析Tomcat类加载机制

    1JVM类加载机制 JVM的ClassLoader通过Parent属性定义父子关系,可以形成树状结构.其中引导类.扩展类.系统类三个加载器是JVM内置的. 它们的作用分别是: 1)引导类加载器:使用n ...

  2. Java Web(三) 会话机制,Cookie和Session详解

    很大一部分应该知道什么是会话机制,也能说的出几句,我也大概了解一点,但是学了之后几天不用,立马忘的一干二净,原因可能是没能好好理解这两种会话机制,所以会一直遗忘,一直重新回过头来学习它,今天好好把他总 ...

  3. Cookie&Seesion会话 共享数据 工作流程 持久化 Servlet三个作用域 会话机制

    Day37 Cookie&Seesion会话 1.1.1 什么是cookie 当用户通过浏览器访问Web服务器时,服务器会给客户端发送一些信息,这些信息都保存在Cookie中.这样,当该浏览器 ...

  4. web容器的会话机制

    基本所有web应用开发的朋友都很熟悉session会话这个概念,在某个特定时间内,我们说可以在一个会话中存储某些状态,需要的时候又可以把状态取出来,这整个过程的时间空间可以抽象成"会话&qu ...

  5. Java Web(三) 会话机制,Cookie和Session详解(转载)

    https://www.cnblogs.com/whgk/p/6422391.html 很大一部分应该知道什么是会话机制,也能说的出几句,我也大概了解一点,但是学了之后几天不用,立马忘的一干二净,原因 ...

  6. 关于tomcat session机制梳理

     一道题目引起的思考:"tomcat里怎样禁止服务端自己主动创建session". 1背景知识: 要说tomcat的机制.先从session说起. http是无状态协议(http详 ...

  7. 深入剖析TOMCAT

    理解tomcat之搭建简易http服务器 做过java web的同学都对tomcat非常熟悉.我们在使用tomcat带来的便利的同时,是否想过tomcat是如何工作的呢?tomcat本质是一个http ...

  8. web开发(三) 会话机制,Cookie和Session详解

    在网上看见一篇不错的文章,写的详细. 以下内容引用那篇博文.转载于<http://www.cnblogs.com/whgk/p/6422391.html>,在此仅供学习参考之用. 一.会话 ...

  9. 会话机制,Cookie和Session详解

    转载自:https://www.cnblogs.com/whgk/p/6422391.html 很大一部分应该知道什么是会话机制,也能说的出几句,我也大概了解一点,但是学了之后几天不用,立马忘的一干二 ...

随机推荐

  1. Django中数据查询(万能下换线,聚合,F,Q)

    数据查询中万能的下划线基本用法: __contains: 包含 __icontains: 包含(忽略大小写) __startswith: 以什么开头 __istartswith: 以什么开头(忽略大小 ...

  2. Saltstack基础

    salt介绍 salt简单介绍 一个基础平台管理工具 一个配置管理系统,能够维护预定义状态的远程节点 一个分布式远程执行系统,用来在远程节点上执行命令和查询数据 salt的核心功能 使命令发送到远程系 ...

  3. typedef的基本用法

    1. 四个用途 用途一: 定义一种类型的别名,而不只是简单的宏替换.可以用作同时声明指针型的多个对象.比如: char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针 ...

  4. JQuery插件的写法和规范

    首先,在具体说明编写插件之前,我们先假定一个使用场景:有一个HTML页面(或.aspx页面),页面上放置了一个5行3列的表格,即:<table></table>标记,具体代码如 ...

  5. Java的数组排序

    对数组进行排序 使用到的排序算法有: 1 选择排序   2 冒泡排序   3 插入排序    4 JavaAPI提高排序算法 选择排序的原理: 1 将数组中每个元素与第一个元素比较,如果这个元素小于第 ...

  6. App上架应用市场,如何攻破安全过检难题

    App的安全过检与众所熟知的安全检测是两个完全不同的概念.首先App行业本身对App安全过检有一定的要求与规范,其次2017年6月1日正式实施的<中国网络安全法>中就曾要求App在渠道上线 ...

  7. java.sql.SQLException: **** [SQLServer]对象名 "XXXX"无效

    原因:进到数据库里面,但是没有选择数据库. 方法:检查数据源配置,这玩意容易看出.难得是多数据库操作时,切换数据源!

  8. 算法导轮之B树的学习

    最近学习了算法导轮里B树相关的知识,在此写一篇博客作为总结. 1.引言 B树是为磁盘或其他直接存取的辅助存储设备而设计的一种平衡搜索树.B树类似于红黑树,但它与红黑树最大不同之处在于B树的节点可以拥有 ...

  9. node之fs模块

    前言 fs是filesystem的缩写,该模块提供本地文件的读写能力,基本上是POSIX文件操作命令的简单包装.但是,这个模块几乎对所有操作提供异步和同步两种操作方式,供开发者选择. 一.文件读取 文 ...

  10. 开源Spring解决方案--lm.solution

    Github 项目地址: https://github.com/liumeng0403/lm.solution 一.说明 1.本项目未按java项目传统命名方式命名项目名,包名 如:org.xxxx. ...