UDN-企业互联网技术人气社区

板块导航

浏览  : 626
回复  : 1

[讨论交流] 对于过期的session,Tomcat做了什么?

[复制链接]
开花包的头像 楼主
发表于 2016-7-18 21:15:16 | 显示全部楼层 |阅读模式
  他们已经被风吹走,散落在天涯。

  ---- <那些花儿>

  对于应用中的Session,我们都知道会有过期的概念。一个session过期之后,会被Tomcat做什么处理?

  很久之前看过相关源码,有些零星的记忆。

  今天群里有个群友问到这个问题,我大概印象里是记得一个超时判断,而对于是否有后台线程不定期判断还真没细看过。

  群友提到是否后台线程不断轮询?我特意看了下代码,这里总结下,分享给各位。

  1. 创建

  Tomcat在启动的时候,是以组件的形式,从父容器开始启动,并遍历启动该容器中的子容器。而Tomcat中存在一个ContainerBase的类,是各个容器的父类,在该类中有每个容器启动时会调用的startInternal方法。

  这个方法的最后,有这样一行代码:

  1.  // Start our thread
  2. threadStart();
复制代码


  是要启动一个线程的样子。方法对应的内容如下:

  1. /**
  2. * Start the background thread that will periodically check for
  3. * session timeouts.
  4. */
  5. protected void threadStart() {

  6.     if (thread != null)
  7.         return;
  8.     if (backgroundProcessorDelay <= 0)  //注意这里,由于大多数容器的这个值都是-1,因此最终只有Engine的线程启动
  9.         return;

  10.     threadDone = false;
  11.     String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
  12.     thread = new Thread(new ContainerBackgroundProcessor(), threadName);
  13.     thread.setDaemon(true);
  14.     thread.start();
  15. }
复制代码


  我们上面提到,这个方法是各个容器的父类中的方法,且会被多次调用,理论上是会创建多个线程的。重点在于我加红字注释的地方,这里的backgroundProcessDelay默认值是-1,所以多数的容器都不会创建线程。而只有Engine容器在初始化时将该值设置成了10,因此只有Engine中会创建一个线程。

  2. 执行

  我们来看这个创建线程要做哪些事情?

  1. 我们来看这个创建线程要做哪些事情?

  2. /**
  3. * Private thread class to invoke the backgroundProcess method
  4. * of this container and its children after a fixed delay.
  5. */
  6. protected class ContainerBackgroundProcessor implements Runnable {
  7.     public void run() {
  8.         try {
  9.             while (!threadDone) {
  10.                 try {
  11.                  Thread.sleep(backgroundProcessorDelay * 1000L);
  12.                 } catch (InterruptedException e) {}
  13.                 if (!threadDone) {
  14.                     processChildren(ContainerBase.this);
  15.                 }}
  16.         } catch (RuntimeException|Error e) {}    }

  17.     protected void processChildren(Container container) {
  18.         try {
  19.            container.backgroundProcess();
  20.             Container[] children = container.findChildren();
  21.             for (int i = 0; i < children.length; i++) {
  22.             if (children[i].getBackgroundProcessorDelay() <= 0) {
  23.                     processChildren(children[i]);//递归调用方法
  24.                 }}
  25.         } catch (Throwable t) {}
  26.     }}
复制代码

  我们看到,整个线程的执行频率,是根据设置的backgroundProcessDelay参数来决定的。前面提到,这个参数只有Engine中设置成了10,因此整个线程的执行频率就是每10秒一次。

  执行时,先执行本容器的backgroundProcess逻辑,之后,遍历所有子容器,对于参数值小于等于0的,继续处理子容器的逻辑。

  Engine组件的backgroundProcessorDelay这个属性中官方文档描述如下:

This value represents the delay in seconds between the invocation of the backgroundProcess method on this engine and its child containers, including all hosts and contexts. Child containers will not be invoked if their delay value is not negative (which would mean they are using their own processing thread). Setting this to a positive value will cause a thread to be spawn. After waiting the specified amount of time, the thread will invoke the backgroundProcess method on this engine and all its child containers. If not specified, the default value for this attribute is 10, which represent a 10 seconds delay.

  和上面的代码高度吻合,同时也提到,如果这个参数如果大子容器中被改为非负数,则子容器的后台处理将不会在此执行,那个时候,就是各个子容器自己控制后台线程的执行频率了。

  有趣的地方是,这个参数是写在所有组件的父类ContainerBase这个类中,所以,每个组件可以定义其自己的backgroundProcessorDelay属性值。而且每个组件可以配置此属性值来确认自已及自己的子容器是否需要运行backgroundProcess。而这个值默认是-1,代表的意义是自己的运行周期依赖父容器的周期,不做单独配置。而最终的值就是通过Engine传入的,所以后面子容器的执行周期默认都是10秒。

  3. 过期Session

  过期的session,也正是基于上面提到的后面线程执行的清理操作。触发点是在父容器一路调用到Context容器时,每个Context都有当前应用对应的session管理器 -- SessionManager。

  Context容器后台线程代码是这个样子:

  1. public void backgroundProcess() {
  2.     Loader loader = getLoader();
  3.     if (loader != null) {
  4.         try {
  5.             loader.backgroundProcess();//---------1
  6.         } catch (Exception e) { }
  7.     }
  8.     Manager manager = getManager();
  9.     if (manager != null) {
  10.         try {
  11.             manager.backgroundProcess(); //看这里。
  12.         } catch (Exception e) {}
  13.     }
  14.     WebResourceRoot resources = getResources();
  15.     if (resources != null) {
  16.         try {
  17.             resources.backgroundProcess(); //-----------2
  18.         } catch (Exception e) {}
  19.     }
  20.     super.backgroundProcess();
  21. }
复制代码


  在Context拿到对应的Session管理器之后,我们来看其内部又要做些什么

  我们再看,Manager内部的后台处理,是这样一种执行策略:

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

  也就是,不是每一次的调用,都会涉及到session的过期操作,而是有一定的执行频率。

  两个参数,一个是递归次数,一个是执行频率,声明如下:

  1. /**
  2. * Iteration count for background processing.
  3. */
  4. private int count = 0;
  5. /**
  6. * Frequency of the session expiration, and related manager operations.
  7. * Manager operations will be done once for the specified amount of
  8. * backgrondProcess calls (ie, the lower the amount, the most often the
  9. * checks will occur).
  10. */
  11. protected int processExpiresFrequency = 6;
复制代码


  而具体的session过期之后,清除数据之类的操作,和之前记忆里的还算一致,相关主要代码如下:

  1. /**
  2. * Invalidate all sessions that have expired.
  3. */
  4. public void processExpires() {
  5.     Session sessions[] = findSessions();
  6.     int expireHere = 0 ;
  7.     for (int i = 0; i < sessions.length; i++) {
  8.         if (sessions[i]!=null && !sessions[i].isValid()) {//这一句是关键
  9.             expireHere++;
  10. }}}
复制代码


  判断是否是合法session的方法中,调用到了过期操作。

  1. public boolean isValid() {
  2.     if (!this.isValid) {
  3.         return false;}
  4.     if (this.expiring) {
  5.         return true;}
  6.     if (ACTIVITY_CHECK && accessCount.get() > 0) {
  7.         return true;}

  8.     if (maxInactiveInterval > 0) {
  9.         int timeIdle = (int) (getIdleTimeInternal() / 1000L);
  10.         if (timeIdle >= maxInactiveInterval) {
  11.             expire(true); //就是这里了。
  12.         }}
  13.     return this.isValid;}
复制代码


  过期的时候,最主要的是删除session内对应的一些属性值等,同时会通知各类的Listener,以触发其对应的事件。

  1. public void expire(boolean notify) {
  2.     if (!isValid)
  3.         return;
  4.     synchronized (this) {
  5.         if (expiring || !isValid)
  6.             return;
  7.         // We have completed expire of this session
  8.         setValid(false);
  9.         expiring = false;
  10.         // Unbind any objects associated with this session
  11.         String keys[] = keys();
  12.             for (int i = 0; i < keys.length; i++) {
  13.                 removeAttributeInternal(keys[i], notify);
  14.             }    }}
复制代码


  这个方法的代码没什么特殊的地方,写在这里是顺道展示下Tomcat内部对于

  DCL的使用,即双重锁检查。对相关内容感兴趣的朋友,可以重点关注下这块内容。重点是要把isValid这个参数声明为volatile,来达到每次修改内存可见的目的。

  整个session过期之后的处理流程,就是上面的内容。

  他们已经被后台定时线程清除,移出内存啦。

  ------------<那些session>

  我看的时候,是先从isValid这个方法入手的。直接从调用开始的,你呢?

c.webp.jpg


  PS.

  我在上面有一行代码的后面,注释写的是------1这种形式,这里是后台定时线程执行时做的另外一件事,即应用的重新加载,我们使用Tomcat过程中,有时会发现,修改了某个应用的配置文件时,应用自动重新reload了。这个就是后台线程顺道做的。感兴趣的朋友可以看WebappLoader类。

  PPS.

  还有一行代码是-------2这种注释,也是后台线程做的一件事,即清除WebApp的资源的,感兴趣的朋友可以看StandardRoot类。

原文作者:chainhou 来源:开发者头条
发表于 2016-8-8 15:34:06 | 显示全部楼层
来学习了
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部