您的位置:首页 > 数据库 > Redis

tomcat-redis-session-manager 介绍

2014-08-22 09:53 465 查看

1缓存机制

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

例如下面的配置:

[html]
view plaincopy

<ManagerclassNameManagerclassName="org.apache.catalina.session.PersistentManager"  
          debug="0"  
          saveOnRestart="true"  
          maxActiveSession="-1"  
          minIdleSwap="0"  
          maxIdleSwap="0"  
          maxIdleBackup="-1">  
          <StoreclassNameStoreclassName="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源码。

[java]
view plaincopy

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()中统一处理的。

[java]
view plaincopy

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的源码为例。

[java]
view plaincopy

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。

[java]
view plaincopy

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中。

[java]
view plaincopy

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

转载:http://blog.csdn.net/dc_726/article/details/11821235
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐