Tomcat的Session管理

cookie的值, 可以结合chrome F12 下的  Resource 和Network查看

Session对象的创建一般是源于这样的一条语句: 

Session session = request.getSession(false);或者Session session = request.getSession();如果不在乎服务器压力可能多那么一点点的话。 

在Tomcat的实现中,这个request是org.apache.catalina.connector.Request类的包装类org.apache.catalina.connector.RequestFacade的对象,它的两个#getSession()方法如下: 

Java代码   收藏代码
  1. public HttpSession getSession(boolean create) {  
  2.     if (request == null) {  
  3.         throw new IllegalStateException(  
  4.                         sm.getString("requestFacade.nullRequest"));  
  5.     }  
  6.   
  7.     if (SecurityUtil.isPackageProtectionEnabled()){  
  8.         return (HttpSession)AccessController.  
  9.             doPrivileged(new GetSessionPrivilegedAction(create));  
  10.     } else {  
  11.         return request.getSession(create);  
  12.     }  
  13. }  


Java代码   收藏代码
  1. public HttpSession getSession() {  
  2.     if (request == null) {  
  3.         throw new IllegalStateException(  
  4.                         sm.getString("requestFacade.nullRequest"));  
  5.     }  
  6.   
  7.     return getSession(true);  
  8. }  


其实差不太多,最后都会进入org.apache.catalina.connector.Request的#getSession()方法。这个方法的源代码如下: 

Java代码   收藏代码
  1. public HttpSession getSession(boolean create) {  
  2.     Session session = doGetSession(create);  
  3.     if (session != null) {  
  4.         return session.getSession();  
  5.     } else {  
  6.         return null;  
  7.     }  
  8. }  


然后调用就到了#doGetSession()这个方法了。源代码如下 

Java代码   收藏代码
  1. protected Session doGetSession(boolean create) {  
  2.     // 没有Context的话直接返回null  
  3.     if (context == null)  
  4.         return (null);  
  5.   
  6.     // 判断Session是否有效  
  7.     if ((session != null) && !session.isValid())  
  8.         session = null;  
  9.     if (session != null)  
  10.         return (session);  
  11.   
  12.     // 返回Manager对象,这里是StandardManager类的对象  
  13.     Manager manager = null;  
  14.     if (context != null)  
  15.         manager = context.getManager();  
  16.     if (manager == null)  
  17.         return (null); // Sessions are not supported  
  18.     // 判断是否有SessionID  
  19.     if (requestedSessionId != null) {  
  20.         try {  
  21.             // 在Manager中根据SessionID查找Session  
  22.             session = manager.findSession(requestedSessionId);  
  23.         } catch (IOException e) {  
  24.             session = null;  
  25.         }  
  26.         if ((session != null) && !session.isValid())  
  27.             session = null;  
  28.         if (session != null) {  
  29.             // 更新访问时间  
  30.             session.access();  
  31.             return (session);  
  32.         }  
  33.     }  
  34.   
  35.     // 创建新的Session  
  36.     if (!create)  
  37.         return (null);  
  38.     if ((context != null) && (response != null) && context.getCookies()  
  39.             && response.getResponse().isCommitted()) {  
  40.         throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));  
  41.     }  
  42.   
  43.     // 判断是否使用 "/" 作为Session Cookie的存储路径 并且 是否SessionID来自Cookie  
  44.     if (connector.getEmptySessionPath() && isRequestedSessionIdFromCookie()) {  
  45.         // 创建Session  
  46.         session = manager.createSession(getRequestedSessionId());  
  47.     } else {  
  48.         session = manager.createSession(null);  
  49.     }  
  50.   
  51.     // 创建一个新的Session Cookies  
  52.     if ((session != null) && (getContext() != null) && getContext().getCookies()) {  
  53.         Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME, session.getIdInternal());  
  54.         // 配置Session Cookie  
  55.         configureSessionCookie(cookie);  
  56.         // 在响应中加入Session Cookie  
  57.         response.addCookieInternal(cookie);  
  58.     }  
  59.   
  60.     if (session != null) {  
  61.         // 更新访问时间  
  62.         session.access();  
  63.         return (session);  
  64.     } else {  
  65.         return (null);  
  66.     }  
  67.   
  68. }  

这个方法说明了Session创建的大致过程,首先判断requestedSessionId是否存在,如果存在,那么根据这个ID去查找Session对象。如果requestedSessionId不存在或者没有取到Session,并且传递给#getSession(boolean)的参数为真,那么要创建一个新的Session,并且给客户端写回去一个Session Cookie。 

首先,我感兴趣的是requestedSessionId的赋值,它到底是什么时候被赋值的呢? 

还要向回看Tomcat的请求处理过程,请求曾到过这一步,org.apache.catalina.connector.CoyoteAdapter的#service()方法。里边有这样一句方法调用:postParseRequest(req, request, res, response)。就是这一步处理了SessionID的获取,这个方法调用了#parseSessionId()和parseSessionCookiesId()这两个方法,就是它对Session ID进行了提取,源代码分别如下: 

Java代码   收藏代码
  1. protected void parseSessionId(org.apache.coyote.Request req, Request request) {  
  2.   
  3.     ByteChunk uriBC = req.requestURI().getByteChunk();  
  4.     // 判断URL中是不是有";jsessionid="这个字符串  
  5.     int semicolon = uriBC.indexOf(match, 0, match.length(), 0);  
  6.   
  7.     if (semicolon > 0) {  
  8.         // Parse session ID, and extract it from the decoded request URI  
  9.         // 在URL中提取Session ID  
  10.         int start = uriBC.getStart();  
  11.         int end = uriBC.getEnd();  
  12.   
  13.         int sessionIdStart = semicolon + match.length();  
  14.         int semicolon2 = uriBC.indexOf(';', sessionIdStart);  
  15.         if (semicolon2 >= 0) {  
  16.             request.setRequestedSessionId(new String(uriBC.getBuffer(), start + sessionIdStart,  
  17.                     semicolon2 - sessionIdStart));  
  18.             byte[] buf = uriBC.getBuffer();  
  19.             for (int i = 0; i < end - start - semicolon2; i++) {  
  20.                 buf[start + semicolon + i] = buf[start + i + semicolon2];  
  21.             }  
  22.             uriBC.setBytes(buf, start, end - start - semicolon2 + semicolon);  
  23.         } else {  
  24.             request.setRequestedSessionId(new String(uriBC.getBuffer(), start + sessionIdStart,  
  25.                     (end - start) - sessionIdStart));  
  26.             uriBC.setEnd(start + semicolon);  
  27.         }  
  28.         // 设定Session ID来自于URL  
  29.         request.setRequestedSessionURL(true);  
  30.   
  31.     } else {  
  32.         request.setRequestedSessionId(null);  
  33.         request.setRequestedSessionURL(false);  
  34.     }  
  35.   
  36. }  


Java代码   收藏代码
  1. protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {  
  2.     Context context = (Context) request.getMappingData().context;  
  3.     if (context != null && !context.getCookies())  
  4.         return;  
  5.   
  6.     // 返回Cookie  
  7.     Cookies serverCookies = req.getCookies();  
  8.     int count = serverCookies.getCookieCount();  
  9.     if (count <= 0)  
  10.         return;  
  11.   
  12.     for (int i = 0; i < count; i++) {  
  13.         ServerCookie scookie = serverCookies.getCookie(i);  
  14.         // 判断是否有JSESSIONID这个名字的Cookie  
  15.         if (scookie.getName().equals(Globals.SESSION_COOKIE_NAME)) {  
  16.             // Override anything requested in the URL  
  17.             if (!request.isRequestedSessionIdFromCookie()) {  
  18.                 // 设定Session ID  
  19.                 convertMB(scookie.getValue());  
  20.                 request.setRequestedSessionId(scookie.getValue().toString());  
  21.                 // 如果之前在URL中读到了SessionID,那么会覆盖它  
  22.                 request.setRequestedSessionCookie(true);  
  23.                 request.setRequestedSessionURL(false);  
  24.                 if (log.isDebugEnabled())  
  25.                     log.debug(" Requested cookie session id is " + request.getRequestedSessionId());  
  26.             } else {  
  27.                 if (!request.isRequestedSessionIdValid()) {  
  28.                     convertMB(scookie.getValue());  
  29.                     request.setRequestedSessionId(scookie.getValue().toString());  
  30.                 }  
  31.             }  
  32.         }  
  33.     }  
  34.   
  35. }  

Tomcat就是通过上边的两个方法读到URL或者Cookie中存放的Session ID的。 

了解了Session ID的获取,下面要看一下Session的查找过程,就是org.apache.catalina.session.StandardManager的#findSession()方法。这个方法是在它的基类中定义的,源代码如下: 

Java代码   收藏代码
  1. public Session findSession(String id) throws IOException {  
  2.     if (id == null)  
  3.         return (null);  
  4.     return (Session) sessions.get(id);  
  5. }  

代码很短,其中sessions是一个ConcurrentHashMap<String, Session>对象。那么这个sessions的对象是什么时候载入的Session呢? 

启动的时候!可以看一下StandardManager#start()方法。最后调用了#load()方法,这个就是载入Session的方法了: 

Java代码   收藏代码
  1. public void load() throws ClassNotFoundException, IOException {  
  2.     if (SecurityUtil.isPackageProtectionEnabled()) {  
  3.         try {  
  4.             AccessController.doPrivileged(new PrivilegedDoLoad());  
  5.         } catch (PrivilegedActionException ex) {  
  6.             Exception exception = ex.getException();  
  7.             if (exception instanceof ClassNotFoundException) {  
  8.                 throw (ClassNotFoundException) exception;  
  9.             } else if (exception instanceof IOException) {  
  10.                 throw (IOException) exception;  
  11.             }  
  12.             if (log.isDebugEnabled())  
  13.                 log.debug("Unreported exception in load() " + exception);  
  14.         }  
  15.     } else {  
  16.         doLoad();  
  17.     }  
  18. }  

最后调用了#doLoad()方法来具体的载入Session,源代码如下: 

Java代码   收藏代码
  1. protected void doLoad() throws ClassNotFoundException, IOException {  
  2.     if (log.isDebugEnabled())  
  3.         log.debug("Start: Loading persisted sessions");  
  4.   
  5.     // 清空Map  
  6.     sessions.clear();  
  7.   
  8.     // 对应work/Catalina/localhost/%app name%/SESSIONS.ser文件  
  9.     File file = file();  
  10.     if (file == null)  
  11.         return;  
  12.     if (log.isDebugEnabled())  
  13.         log.debug(sm.getString("standardManager.loading", pathname));  
  14.     FileInputStream fis = null;  
  15.     ObjectInputStream ois = null;  
  16.     Loader loader = null;  
  17.     ClassLoader classLoader = null;  
  18.     try {  
  19.         // 载入Session缓存文件  
  20.         fis = new FileInputStream(file.getAbsolutePath());  
  21.         BufferedInputStream bis = new BufferedInputStream(fis);  
  22.         if (container != null)  
  23.             loader = container.getLoader();  
  24.         if (loader != null)  
  25.             classLoader = loader.getClassLoader();  
  26.         if (classLoader != null) {  
  27.             if (log.isDebugEnabled())  
  28.                 log.debug("Creating custom object input stream for class loader ");  
  29.             ois = new CustomObjectInputStream(bis, classLoader);  
  30.         } else {  
  31.             if (log.isDebugEnabled())  
  32.                 log.debug("Creating standard object input stream");  
  33.             ois = new ObjectInputStream(bis);  
  34.         }  
  35.     } catch (FileNotFoundException e) {  
  36.         if (log.isDebugEnabled())  
  37.             log.debug("No persisted data file found");  
  38.         return;  
  39.     } catch (IOException e) {  
  40.         log.error(sm.getString("standardManager.loading.ioe", e), e);  
  41.         if (ois != null) {  
  42.             try {  
  43.                 ois.close();  
  44.             } catch (IOException f) {  
  45.                 ;  
  46.             }  
  47.             ois = null;  
  48.         }  
  49.         throw e;  
  50.     }  
  51.   
  52.     synchronized (sessions) {  
  53.         try {  
  54.             // 读出Session个数  
  55.             Integer count = (Integer) ois.readObject();  
  56.             int n = count.intValue();  
  57.             if (log.isDebugEnabled())  
  58.                 log.debug("Loading " + n + " persisted sessions");  
  59.             //  读入Session  
  60.             for (int i = 0; i < n; i++) {  
  61.                 StandardSession session = getNewSession();  
  62.                 session.readObjectData(ois);  
  63.                 session.setManager(this);  
  64.                 sessions.put(session.getIdInternal(), session);  
  65.                 session.activate();  
  66.                 sessionCounter++;  
  67.             }  
  68.         } catch (ClassNotFoundException e) {  
  69.             log.error(sm.getString("standardManager.loading.cnfe", e), e);  
  70.             if (ois != null) {  
  71.                 try {  
  72.                     ois.close();  
  73.                 } catch (IOException f) {  
  74.                     ;  
  75.                 }  
  76.                 ois = null;  
  77.             }  
  78.             throw e;  
  79.         } catch (IOException e) {  
  80.             log.error(sm.getString("standardManager.loading.ioe", e), e);  
  81.             if (ois != null) {  
  82.                 try {  
  83.                     ois.close();  
  84.                 } catch (IOException f) {  
  85.                     ;  
  86.                 }  
  87.                 ois = null;  
  88.             }  
  89.             throw e;  
  90.         } finally {  
  91.             try {  
  92.                 if (ois != null)  
  93.                     ois.close();  
  94.             } catch (IOException f) {  
  95.             }  
  96.   
  97.             // 删除Session缓存文件  
  98.             if (file != null && file.exists())  
  99.                 file.delete();  
  100.         }  
  101.     }  
  102.   
  103.     if (log.isDebugEnabled())  
  104.         log.debug("Finish: Loading persisted sessions");  
  105. }  

大致知道了Session的读取过程,后面就是Session没找到时创建Session的过程了。具体就是org.apache.catalina.session.StandardManager的#createSession()方法: 

Java代码   收藏代码
  1. public Session createSession(String sessionId) {  
  2.     if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) {  
  3.         rejectedSessions++;  
  4.         throw new IllegalStateException(sm.getString("standardManager.createSession.ise"));  
  5.     }  
  6.     return (super.createSession(sessionId));  
  7. }  

最后调用到了它的基类的#createSession()方法了。 

Java代码   收藏代码
  1. public Session createSession(String sessionId) {  
  2.     // 创建一个新的Session  
  3.     Session session = createEmptySession();  
  4.   
  5.     // 初始化Session的属性  
  6.     session.setNew(true);  
  7.     session.setValid(true);  
  8.     session.setCreationTime(System.currentTimeMillis());  
  9.     session.setMaxInactiveInterval(this.maxInactiveInterval);  
  10.     // 如果Session ID为null,那么就生成一个  
  11.     if (sessionId == null) {  
  12.         sessionId = generateSessionId();  
  13.     }  
  14.     session.setId(sessionId);  
  15.     sessionCounter++;  
  16.   
  17.     return (session);  
  18.   
  19. }  

sesseionIdGenerator生成sessionId的算法如下:


[java]  view plain copy
  1. /** 
  2.    * Generate and return a new session identifier. 
  3.    */  
  4.   public String generateSessionId() {  
  5.   
  6.       byte random[] = new byte[16];  
  7.   
  8.       // Render the result as a String of hexadecimal digits  
  9.       StringBuilder buffer = new StringBuilder();  
  10.   
  11.       int resultLenBytes = 0;  
  12.   
  13.       while (resultLenBytes < sessionIdLength) {  
  14.           getRandomBytes(random);  
  15.           for (int j = 0;  
  16.           j < random.length && resultLenBytes < sessionIdLength;  
  17.           j++) {  
  18.               byte b1 = (byte) ((random[j] & 0xf0) >> 4);  
  19.               byte b2 = (byte) (random[j] & 0x0f);  
  20.               if (b1 < 10)  
  21.                   buffer.append((char) ('0' + b1));  
  22.               else  
  23.                   buffer.append((char) ('A' + (b1 - 10)));  
  24.               if (b2 < 10)  
  25.                   buffer.append((char) ('0' + b2));  
  26.               else  
  27.                   buffer.append((char) ('A' + (b2 - 10)));  
  28.               resultLenBytes++;  
  29.           }  
  30.       }  
  31.   
  32.       if (jvmRoute != null && jvmRoute.length() > 0) {  
  33.           buffer.append('.').append(jvmRoute);  
  34.       }  
  35.   
  36.       return buffer.toString();  
  37.   }  

其中jvmRoute是为了防止tomcat集群导致的sessionId冲突,getRandomBytes(random);会通过随机算法生成16byte的字节数组,最终sessionId默认是生成16byte的字符串。


通过上述过程,一个新的Session就创建出来了。


Tomcat会开启一个后台线程每隔一段时间检查Session的有效性,这个线程是在Tomcat启动的时候当StardardEngine启动时随之启动的。可以参看StardardEngine的基类ContainerBase的#threadStart()方法: 

Java代码   收藏代码
  1. protected void threadStart() {  
  2.     if (thread != null)  
  3.         return;  
  4.     if (backgroundProcessorDelay <= 0)  
  5.         return;  
  6.       
  7.     threadDone = false;  
  8.     String threadName = "ContainerBackgroundProcessor[" + toString() + "]";  
  9.     // 开启后台的监控线程  
  10.     thread = new Thread(new ContainerBackgroundProcessor(), threadName);  
  11.     thread.setDaemon(true);  
  12.     thread.start();  
  13.   
  14. }  


这个方法启动了一个ContainerBackgroundProcessor类的线程,这个类重写的#run()方法中包括了对Session的有效性监控。具体的细节就不详细陈述了。每隔一段时间,此线程就会启动一次并调用了ManageBase的#backgroundProcess()方法。其源代码如下: 

Java代码   收藏代码
  1. public void backgroundProcess() {  
  2.     count = (count + 1) % processExpiresFrequency;  
  3.     if (count == 0)  
  4.         processExpires();  
  5. }  


每隔一段时间就会调用processExpires()方法去判断Session的有效性。 

Java代码   收藏代码
  1. public void processExpires() {  
  2.     // 现在的时间  
  3.     long timeNow = System.currentTimeMillis();  
  4.     // 所有的Session对象  
  5.     Session sessions[] = findSessions();  
  6.     int expireHere = 0;  
  7.   
  8.     if (log.isDebugEnabled())  
  9.         log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount "  
  10.                 + sessions.length);  
  11.     for (int i = 0; i < sessions.length; i++) {  
  12.         // 判断并设定Session是否失效  
  13.         if (sessions[i] != null && !sessions[i].isValid()) {  
  14.             expireHere++;  
  15.         }  
  16.     }  
  17.     long timeEnd = System.currentTimeMillis();  
  18.     if (log.isDebugEnabled())  
  19.         log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow)  
  20.                 + " expired sessions: " + expireHere);  
  21.     processingTime += (timeEnd - timeNow);  
  22.   
  23. }  


此方法最终调用了isValid()去判断和设定Session是否失效,源代码如下所示: 

Java代码   收藏代码
  1. public boolean isValid() {  
  2.     // 是否过期  
  3.     if (this.expiring) {  
  4.         return true;  
  5.     }  
  6.     // 是否有效  
  7.     if (!this.isValid) {  
  8.         return false;  
  9.     }  
  10.     // 正在使用中并且访问数大于0  
  11.     if (ACTIVITY_CHECK && accessCount.get() > 0) {  
  12.         return true;  
  13.     }  
  14.   
  15.     if (maxInactiveInterval >= 0) {  
  16.         // 判断Session是否过期  
  17.         long timeNow = System.currentTimeMillis();  
  18.         int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);  
  19.         if (timeIdle >= maxInactiveInterval) {  
  20.             // 设定Session过期  
  21.             expire(true);  
  22.         }  
  23.     }  
  24.   
  25.     return (this.isValid);  
  26. }  

关于cookie的设置在javaEE API中

public void setMaxAge(int expiry)
Sets the maximum age of the cookie in seconds.

A positive value indicates that the cookie will expire after that many seconds have passed. Note that the value is the maximum age when the cookie will expire, not the cookie's current age.

A negative value means that the cookie is not stored persistently and will be deleted when the Web browser exits. A zero value causes the cookie to be deleted.




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
TomcatSession同步是指在Tomcat集群中确保Session的共享和同步。在Tomcat中,Session默认保存在本地内存中。如果我们要部署多个Tomcat实例组成集群,就需要解决Session同步的问题,以便不同的Tomcat实例可以访问和共享同一个Session。 一种解决方法是通过Tomcat自带的cluster方式实现Session的同步。这种方式可以实现多个Tomcat之间的实时Session信息共享。但是随着Tomcat数量和请求量的增加,这种方法的性能会下降相对较厉害。 另一种解决方法是使用负载均衡器来管理Tomcat实例。通过在负载均衡器的配置中指定balanced_workers,可以实现对多个Tomcat实例的负载均衡,并确保Session的同步。 总结来说,TomcatSession同步可以通过Tomcat自带的cluster方式或使用负载均衡器来实现。需要根据具体的需求和环境选择适合的方法来确保Session的共享和同步。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [SpringBoot使用Redis实现Tomcat集群的session同步操作](https://blog.csdn.net/qq_45926602/article/details/129051908)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [Tomcat集群和session会话同步管理](https://blog.csdn.net/Tmeng521/article/details/80212690)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值