Spring Boot内嵌Tomcat session超时问题
作者:互联网
最近让Spring Boot内嵌Tomcat的session超时问题给坑了一把。
在应用中需要设置session超时时间,然后就习惯的在application.properties配置文件中设置如下,
server.session.timeout=90
这里把超时时间设置的短些,主要想看看到底有没有起作用(不能设值30min然后再看吧,那样太不人道了)。结果没起作用,百度下发现Spring Boot 2后,配置变成如下,
server.servlet.session.timeout=90
但结果依然不起作用,后来就断断续续的懵了逼的找问题原因,各种百度,google,最后觉得还是看源代码吧,顺便也学习下。
1. 既然是Session超时时间问题,那就看看对Session的实现 - StandardSession
其中有isValid()方法
/** * Return the <code>isValid</code> flag for this session. */ @Override public boolean isValid() { if (!this.isValid) { return false; } if (this.expiring) { return true; } if (ACTIVITY_CHECK && accessCount.get() > 0) { return true; } if (maxInactiveInterval > 0) { int timeIdle = (int) (getIdleTimeInternal() / 1000L); if (timeIdle >= maxInactiveInterval) { expire(true); } } return this.isValid; }
看了下,这里的 timeIdle >= maxInactiveInterval就是触发session超时的判断,满足则调用 expire(true)。那么问题就来了,什么时候调用isValid()?
2. 后台肯定有定时调用isValid()的线程
查看调用isValid()的相关类如下,StandardManager和ManagerBase入了法眼了。
StandardManager中的注解表明是用来让所有存活的session过期的,应该是在web容器销毁时调用的,所以就只看 ManagerBase
// Expire all active sessions Session sessions[] = findSessions(); for (int i = 0; i < sessions.length; i++) { Session session = sessions[i]; try { if (session.isValid()) { session.expire(); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } finally { // Measure against memory leaking if references to the session // object are kept in a shared field somewhere session.recycle(); } }
ManagerBase,注解表明是我们想要的,接下来看调用processExpires()的类。还是ManagerBase。
/** * Invalidate all sessions that have expired. */ 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 (int i = 0; i < sessions.length; i++) { if (sessions[i]!=null && !sessions[i].isValid()) { expireHere++; } } long timeEnd = System.currentTimeMillis(); if(log.isDebugEnabled()) log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere); processingTime += ( timeEnd - timeNow ); }
调用processExpires()
/** * Frequency of the session expiration, and related manager operations. * Manager operations will be done once for the specified amount of * backgroundProcess calls (ie, the lower the amount, the most often the * checks will occur). */ protected int processExpiresFrequency = 6;
/** * {@inheritDoc} * <p> * Direct call to {@link #processExpires()} */ @Override public void backgroundProcess() { count = (count + 1) % processExpiresFrequency; if (count == 0) processExpires(); }
看到backgroundProcess()方法名就知道离真理不远了。其调用如下,在StandardContext类中,
@Override public void backgroundProcess() { if (!getState().isAvailable()) return; Loader loader = getLoader(); if (loader != null) { try { loader.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString( "standardContext.backgroundProcess.loader", loader), e); } } Manager manager = getManager(); if (manager != null) { try { manager.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString( "standardContext.backgroundProcess.manager", manager), e); } } WebResourceRoot resources = getResources(); if (resources != null) { try { resources.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString( "standardContext.backgroundProcess.resources", resources), e); } } InstanceManager instanceManager = getInstanceManager(); if (instanceManager instanceof DefaultInstanceManager) { try { ((DefaultInstanceManager)instanceManager).backgroundProcess(); } catch (Exception e) { log.warn(sm.getString( "standardContext.backgroundProcess.instanceManager", resources), e); } } super.backgroundProcess(); }
但是还没有看到线程的创建,继续查看调用,ContainerBase.ContainerBackgroundProcessor
/** * Private thread class to invoke the backgroundProcess method * of this container and its children after a fixed delay. */ protected class ContainerBackgroundProcessor implements Runnable
while (!threadDone) { try { Thread.sleep(backgroundProcessorDelay * 1000L); } catch (InterruptedException e) { // Ignore } if (!threadDone) { processChildren(ContainerBase.this); } }
看到曙光了!看来后台线程每隔 backgroundProcessorDelay * processExpiresFrequency (s)来判断session是否过期。
默认值:
backgroundProcessorDelay = 30s
ServerProperties.class /** * Delay between the invocation of backgroundProcess methods. If a duration suffix * is not specified, seconds will be used. */ @DurationUnit(ChronoUnit.SECONDS) private Duration backgroundProcessorDelay = Duration.ofSeconds(30);
processExpiresFrequency = 6
所以默认情况下后台线程每隔3min去判断session是否超时。这样我之前设置server.servlet.session.timeout=90s,没办法看到效果的。
另外还要注意后台对timeout的处理以min为单位,即90s在后台会认为是1min的。
TomcatServletWebServerFactory.class private long getSessionTimeoutInMinutes() { Duration sessionTimeout = getSession().getTimeout(); if (isZeroOrLess(sessionTimeout)) { return 0; } return Math.max(sessionTimeout.toMinutes(), 1); }
标签:backgroundProcess,return,log,Tomcat,sessions,Spring,Boot,session,isValid 来源: https://www.cnblogs.com/hello-yz/p/10993707.html