今天使用一种很临时的方案解决 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
的数量过多。
欢迎关注公众号: