今天使用一种很临时的方案解决 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 持久化策略,主要通过实现不同的 Manager 和 Store 来实现:
- 内存持久化:这是 Tomcat 的默认策略,由
StandardManager实现。所有的 Session 都保存在内存中。当 Tomcat 重启或者出现故障时,所有的 Session 信息都会丢失。 - 文件持久化:由
PersistentManager和FileStore实现。不活跃的 Session 会被保存到文件系统中,从而释放内存。当 Session 再次变得活跃时,可以从文件系统中恢复。 - 数据库持久化:由
PersistentManager和JDBCStore实现。不活跃的 Session 会被保存到数据库中。这种策略适合于需要在多个 Tomcat 实例之间共享 Session 的情况。 - 分布式 Session:在一个 Tomcat 集群中,可以通过实现
ClusterableSession和DeltaManager或BackupManager来实现 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);}
既然怀疑这个函数可能存在定时调度,其实有一个小技巧,在这个函数上打一个断点,看是否可能触发,果然没一会就触发了:

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

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 ittry {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 的数量过多。
欢迎关注公众号:
