前面讲到,注解@EnableRedisHttpSession支持配置maxInactiveIntervalInSeconds属性,默认是1800s.

考虑到这个session的过期时间的设置我们系统可能会有要求,之前做MTN项目的时候就有要求用户30分钟不操作就要将用户踢下线,所以这里设置了60s来验证下这个功能:

@EnableRedisHttpSession(redisNamespace = "iotcmp-cc", maxInactiveIntervalInSeconds = 60)

登录系统,停留一分钟之后再操作,发现被我的拦截器拦截到提示用户未登录或Session超时。说明这个session超时功能是OK的。

下面详细看看这个session超时功能:
我们登录系统创建session之后,查看redis中缓存的key:

"spring:session:iotcmp-cc:expirations:1523286240000"
"spring:session:iotcmp-cc:sessions:expires:add13af0-361d-4ea1-9667-04ee475281c0"
"spring:session:iotcmp-cc:sessions:add13af0-361d-4ea1-9667-04ee475281c0"

检查这三个缓存的key的过期时间:

172.16.22.2:7691> ttl spring:session:iotcmp-cc:expirations:1523286240000
(integer) 299
172.16.22.2:7691> ttl spring:session:iotcmp-cc:sessions:add13af0-361d-4ea1-9667-04ee475281c0
(integer) 276
172.16.22.2:7691> ttl spring:session:iotcmp-cc:sessions:expires:add13af0-361d-4ea1-9667-04ee475281c0
(integer) 35

发现这三个key设置的失效时间并非一致。前两个其实设置的失效时间为360s,第三个设置的失效时间为60s.

这里需要思考下,为什么前两个session key的ttl需要额外加上5min?

查看下源码,根据注解@EnableRedisHttpSession找到处理的类RedisHttpSessionConfiguration:

org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration.setImportMetadata(AnnotationMetadata)
启动的时候在这个方法里读取注解上配置的属性maxInactiveIntervalInSeconds的值

使用redis存储session的实现类:RedisOperationsSessionRepository
org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration.sessionRepository(RedisOperations<Object, Object>, ApplicationEventPublisher)
在这个方法中将maxInactiveIntervalInSeconds的值设置到RedisOperationsSessionRepository的defaultMaxInactiveInterval属性上,描述如下:
org.springframework.session.data.redis.RedisOperationsSessionRepository.setDefaultMaxInactiveInterval(int)

     /**
     * Sets the maximum inactive interval in seconds between requests before newly created
     * sessions will be invalidated. A negative time indicates that the session will never
     * timeout. The default is 1800 (30 minutes).
     *
     * @param defaultMaxInactiveInterval the number of seconds that the {@link Session}
     * should be kept alive between client requests.
     */
    public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
        this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
    }

再看RedisOperationsSessionRepository这个类,对session过期的策略:

 提供类RedisSessionExpirationPolicy来处理session过期策略:
    public RedisOperationsSessionRepository(
            RedisOperations<Object, Object> sessionRedisOperations) {
        Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null");
        this.sessionRedisOperations = sessionRedisOperations;
        this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations,
                this);
    }


提供了一个定时器定时清理过期的session,这里配置的每隔1分钟执行一次
    @Scheduled(cron = "${spring.session.cleanup.cron.expression:0 * * * * *}")
    public void cleanupExpiredSessions() {
        this.expirationPolicy.cleanExpiredSessions();
    }


更新redis中的session
/**
         * Saves any attributes that have been changed and updates the expiration of this
         * session.
         */
        private void saveDelta() {

            XXXXXXXXXXXXXXXXX

            Long originalExpiration = this.originalLastAccessTime == null ? null
                    : this.originalLastAccessTime + TimeUnit.SECONDS
                            .toMillis(getMaxInactiveIntervalInSeconds());
            RedisOperationsSessionRepository.this.expirationPolicy
                    .onExpirationUpdated(originalExpiration, this);
        }

主要看org.springframework.session.data.redis.RedisSessionExpirationPolicy.onExpirationUpdated(Long, ExpiringSession):

    public void onExpirationUpdated(Long originalExpirationTimeInMilli,
            ExpiringSession session) {

        //expires:eebd8f7a-7c4a-4f1a-bbf2-cb84795aa739
        String keyToExpire = "expires:" + session.getId();
        long toExpire = roundUpToNextMinute(expiresInMillis(session));

        if (originalExpirationTimeInMilli != null) {
            long originalRoundedUp = roundUpToNextMinute(originalExpirationTimeInMilli);
            if (toExpire != originalRoundedUp) {
                String expireKey = getExpirationKey(originalRoundedUp);
                this.redis.boundSetOps(expireKey).remove(keyToExpire);
            }
        }

        long sessionExpireInSeconds = session.getMaxInactiveIntervalInSeconds();

        //spring:session:iotcmp-cc:sessions:expires:eebd8f7a-7c4a-4f1a-bbf2-cb84795aa739
        //maxInactiveIntervalInSeconds
        String sessionKey = getSessionKey(keyToExpire);

        if (sessionExpireInSeconds < 0) {
            this.redis.boundValueOps(sessionKey).append("");
            this.redis.boundValueOps(sessionKey).persist();
            this.redis.boundHashOps(getSessionKey(session.getId())).persist();
            return;
        }

        //spring:session:iotcmp-cc:expirations:1523290380000
        //300+maxInactiveIntervalInSeconds 
        String expireKey = getExpirationKey(toExpire);
        BoundSetOperations<Object, Object> expireOperations = this.redis
                .boundSetOps(expireKey);
        expireOperations.add(keyToExpire);

        long fiveMinutesAfterExpires = sessionExpireInSeconds
                + TimeUnit.MINUTES.toSeconds(5);

        expireOperations.expire(fiveMinutesAfterExpires, TimeUnit.SECONDS);
        if (sessionExpireInSeconds == 0) {
            this.redis.delete(sessionKey);
        }
        else {
            this.redis.boundValueOps(sessionKey).append("");
            this.redis.boundValueOps(sessionKey).expire(sessionExpireInSeconds,
                    TimeUnit.SECONDS);
        }

        //spring:session:iotcmp-cc:sessions:eebd8f7a-7c4a-4f1a-bbf2-cb84795aa739
        //300+maxInactiveIntervalInSeconds 
        this.redis.boundHashOps(getSessionKey(session.getId()))
                .expire(fiveMinutesAfterExpires, TimeUnit.SECONDS);
    }

上述方法可以知道,session在redis中保存的这三个key:

"spring:session:iotcmp-cc:sessions:expires:53cb02aa-9f5f-4cf9-812b-c2db72ed4fb1"
"spring:session:iotcmp-cc:expirations:1523257920000"
"spring:session:iotcmp-cc:sessions:53cb02aa-9f5f-4cf9-812b-c2db72ed4fb1"

第一个key 存的值是value类型,key格式:spring:session:redisNamespace:sessions:expires:sessionId;该数据的 ttl表示 sessionId 过期的剩余时间,即 maxInactiveInterval
第二个key 存的值是set类型,key格式:spring:session:redisNamespace:expirations:longtime;longtime表示从 1970 年 1 月 1 日 0 点 0 分到现在经过的分钟数
第三个key 存的值是hash类型,key格式:spring:session:redisNamespace:sessions:sessionId;session保存的数据,记录了 creationTime,maxInactiveInterval,lastAccessedTime,attribute。前两个数据是用于 session 过期管理的辅助数据结构。

我们再查看下每个key分别存放了什么信息:

172.16.22.2:7691> get spring:session:iotcmp-cc:sessions:expires:53cb02aa-9f5f-4cf9-812b-c2db72ed4fb1
""

172.16.22.2:7691> smembers spring:session:iotcmp-cc:expirations:1523257920000
1) "\xac\xed\x00\x05t\x00,expires:53cb02aa-9f5f-4cf9-812b-c2db72ed4fb1"

172.16.22.2:7691> hkeys spring:session:iotcmp-cc:sessions:53cb02aa-9f5f-4cf9-812b-c2db72ed4fb1
1) "sessionAttr:randCheckCode"
2) "maxInactiveInterval"
3) "creationTime"
4) "lastAccessedTime"
5) "sessionAttr:USER_LOGIN_INFO"

定期清理Job调用的方法:org.springframework.session.data.redis.RedisSessionExpirationPolicy.cleanExpiredSessions()

public void cleanExpiredSessions() {
        long now = System.currentTimeMillis();
        long prevMin = roundDownMinute(now);

        if (logger.isDebugEnabled()) {
            logger.debug("Cleaning up sessions expiring at " + new Date(prevMin));
        }

        String expirationKey = getExpirationKey(prevMin);
        Set<Object> sessionsToExpire = this.redis.boundSetOps(expirationKey).members();
        this.redis.delete(expirationKey);
        for (Object session : sessionsToExpire) {
            String sessionKey = getSessionKey((String) session);
            touch(sessionKey);
        }
    }

results matching ""

    No results matching ""