每天学习一点点之 Tomcat 是如何清除过期 Session 的
  6DOeteBHjp6w 2023年12月05日 38 0


今天使用一种很临时的方案解决 Session 泄漏的问题:缩短 Session 的过期时间。这种方法虽然简单,但却非常有效。然而,这引发了一个问题:我们应该将过期时间设置为多短呢?在 Spring Boot 中,最短的过期时间是 60 秒。如果你配置的值小于 60 秒,系统会将其默认设置为 60 秒。这是由 org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getSessionTimeoutInMinutes 方法决定的:

private long getSessionTimeoutInMinutes() {
		Duration sessionTimeout = getSession().getTimeout();
		if (isZeroOrLess(sessionTimeout)) {
			return 0;
		}
		return Math.max(sessionTimeout.toMinutes(), 1);
	}

这时候组内有同学说,过短的过期时间是否会影响服务的性能,例如,是否需要更频繁地删除 Session

为了解答这个问题,我们需要理解 “Tomcat 是如何清除 Session 的”,以及 “Tomcat 中 Session 的更新机制是否会受到 Session 过期时间设置的长短的影响”。

其实根据经验,结合 Redis 删除过期 Key,以及 ThreadLocal 清除过期 entry 等存在类似场景的框架,过期清除机制无非就是两种策略,以及一些细微的处理差异:

  • 惰性删除:只在数据被访问时才检查和删除过期的数据
  • 每次只检查当前数据
  • 顺带检查周边数据
  • 定期删除:后台定时进行扫描
  • 全量扫描
  • 随机扫描

我相信 Tomcat 的处理方式也不会脱离这两种策略。带着这个思考,那么接下来就源码分析一下 Tomcat 到底是如何清除过期 Session 的。

关于 Session,Tomcat 提供了一个常用的函数 org.apache.catalina.session.StandardSession#isValid,用于判断 Session 是否有效。如果 Session 已经无效,该函数会调用 org.apache.catalina.session.StandardSession#expire(boolean) 使 Session 过期(移除)。这表明 Tomcat 对 Session 的处理采用了惰性删除机制。

Tomcat 提供了几种不同的 Session 持久化策略,主要通过实现不同的 ManagerStore 来实现:

  1. 内存持久化:这是 Tomcat 的默认策略,由 StandardManager 实现。所有的 Session 都保存在内存中。当 Tomcat 重启或者出现故障时,所有的 Session 信息都会丢失。
  2. 文件持久化:由 PersistentManagerFileStore 实现。不活跃的 Session 会被保存到文件系统中,从而释放内存。当 Session 再次变得活跃时,可以从文件系统中恢复。
  3. 数据库持久化:由 PersistentManagerJDBCStore 实现。不活跃的 Session 会被保存到数据库中。这种策略适合于需要在多个 Tomcat 实例之间共享 Session 的情况。
  4. 分布式 Session:在一个 Tomcat 集群中,可以通过实现 ClusterableSessionDeltaManagerBackupManager 来实现 Session 的复制或者备份,从而实现 Session 的分布式管理。

本文关注的是内存持久化策略,即 StandardManager。它有一个函数 org.apache.catalina.session.ManagerBase#processExpires,该函数遍历所有的 Session,对每个 Session 判断是否过期。如果发现 Session 已失效,就会让 Session 过期:

public void processExpires() {

        long timeNow = System.currentTimeMillis();
        Session sessions[] = findSessions();
        int expireHere = 0;

        if (log.isDebugEnabled()) {
            log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
        }
        for (Session session : sessions) {
            if (session != null && !session.isValid()) {
                expireHere++;
            }
        }
        long timeEnd = System.currentTimeMillis();
        if (log.isDebugEnabled()) {
            log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) +
                    " expired sessions: " + expireHere);
        }
        processingTime += (timeEnd - timeNow);

    }

既然怀疑这个函数可能存在定时调度,其实有一个小技巧,在这个函数上打一个断点,看是否可能触发,果然没一会就触发了:

每天学习一点点之 Tomcat 是如何清除过期 Session 的_apache

根据 stack trace,这个函数是由 ContainerBackgroundProcessor 执行的。

每天学习一点点之 Tomcat 是如何清除过期 Session 的_firefox_02

Tomcat 启动时会执行 threadStart 方法,该方法基于 java.util.concurrent.ScheduledExecutorService 启动一个定时任务,backgroundProcessorDelay 参数可以控制启动后多久开始执行,backgroundProcessorDelay 控制多久执行一次。

/**
     * Start the background thread that will periodically check for session timeouts.
     */
    protected void threadStart() {
        if (backgroundProcessorDelay > 0 &&
                (getState().isAvailable() || LifecycleState.STARTING_PREP.equals(getState())) &&
                (backgroundProcessorFuture == null || backgroundProcessorFuture.isDone())) {
            if (backgroundProcessorFuture != null && backgroundProcessorFuture.isDone()) {
                // There was an error executing the scheduled task, get it and log it
                try {
                    backgroundProcessorFuture.get();
                } catch (InterruptedException | ExecutionException e) {
                    log.error(sm.getString("containerBase.backgroundProcess.error"), e);
                }
            }
            backgroundProcessorFuture = Container.getService(this).getServer().getUtilityExecutor()
                    .scheduleWithFixedDelay(new ContainerBackgroundProcessor(), c,
                            backgroundProcessorDelay, TimeUnit.SECONDS);
        }
    }

这表明 Tomcat 对 Session 的处理也有定期删除机制。

总结

在 Tomcat 中,Session 的管理采用了惰性删除和定期删除两种策略:

  • 惰性删除是通过 org.apache.catalina.session.StandardSession#isValid 方法实现的,每次在判断 Session 是否有效的时候,如果 Session 已经无效,就会让 Session 过期(移除)
  • 在内存持久化策略中在内存持久化策略中,定期删除是通过 org.apache.catalina.session.ManagerBase#processExpires 方法实现的,该方法会定期遍历所有的 Session,对每个 Session 判断是否过期,如果发现 Session 已失效,就会让 Session 过期

在 Tomcat 中,Session 的更新机制并不会直接受到 Session 过期时间设置的长短的影响,但是,如果 Session 的数量过多,可能导致 Session 清理操作的效率降低。因为在每次 Session 清理操作时,都需要遍历所有的 Session,检查每个 Session 是否过期,如果过期就将其移除。如果 Session 的数量过多,那么这个过程就会消耗更多的时间和资源。

因此,虽然 Session 的过期时间设置不会直接影响 Session 的更新机制,但是 Session 的数量过多确实会对 Session 的更新效率和系统性能产生影响。为了保持系统的高效运行,应该尽量控制 Session 的数量,避免 Session 的数量过多。

欢迎关注公众号:

每天学习一点点之 Tomcat 是如何清除过期 Session 的_学习_03


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月05日 0

暂无评论

推荐阅读
6DOeteBHjp6w