zoukankan      html  css  js  c++  java
  • 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);
        }
  • 相关阅读:
    解决文字溢出,换行等问题
    js获取年、月、日、时、分、秒
    JQuery EasyUI DataGrid动态合并单元格
    JQuery EasyUI Combobox联动
    JQuery EasyUI 读取设置input
    JQuery EasyUI DataGrid获取当前行索引及快速清空
    jQuery EasyUI combobox多选及赋值
    JQuery EasyUI DataGrid 、tree查询
    HTML元素ID和Name区别
    JQuery EasyUI之DataGrid列名和数据列分别设置不同对齐方式(转)
  • 原文地址:https://www.cnblogs.com/hello-yz/p/10993707.html
Copyright © 2011-2022 走看看