当前博客只是自己发现问题的历程,也可能只是适合当前系统,所以当前并不具备一定的代表性。
在开发过程中出现Redis session expire time: xxxx is less than Session timeout:xxx这种问题,虽然不影响功能的使用,但是对于有洁癖的人来说还是有点受不了的。如下图所示:
通过对源码分析及调试:
RedisSessionDAO:
private void saveSession(Session session) throws UnknownSessionException {
if (session == null || session.getId() == null) {
logger.error("session or session id is null");
throw new UnknownSessionException("session or session id is null");
}
byte[] key;
byte[] value;
try {
key = keySerializer.serialize(getRedisSessionKey(session.getId()));
value = valueSerializer.serialize(session);
} catch (SerializationException e) {
logger.error("serialize session error. session id=" + session.getId());
throw new UnknownSessionException(e);
}
if (expire == DEFAULT_EXPIRE) {
this.redisManager.set(key, value, (int) (session.getTimeout() / MILLISECONDS_IN_A_SECOND));
return;
}
if (expire != NO_EXPIRE && expire * MILLISECONDS_IN_A_SECOND < session.getTimeout()) {
logger.warn("Redis session expire time: "
+ (expire * MILLISECONDS_IN_A_SECOND)
+ " is less than Session timeout: "
+ session.getTimeout()
+ " . It may cause some problems.");
}
this.redisManager.set(key, value, expire);
}
可以看到由于expire 与session.getTimeout比较,redisSessionDAO中设置的时间与session中的时间问题。其实主要就是这个session的问题。我的问题是:RedisSessionDAO设置的过期时间A小于配置的session的过期时间B,包含DefaultWebSessionManager.setGlobalSessionTimeout、DefaultWebSessionManager.getSessionIdCookie().setMaxAge(LongValue)都比A要大,故而出现如上图所示的问题。
注意:如果redis中存在session的过期时间仍然超过RedisSessionDAO的过期时间,还是会出现如上图所示的问题,可以通过flushall刷新redis缓存数据。
redis-cli.sh
flushall
由于主要配置的是DefaultWebSessionManager,如下所示:
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setCacheManager(redisCacheManager());
sessionManager.setGlobalSessionTimeout(12 * 3600 * 1000);// milliseconds
sessionManager.getSessionIdCookie().setMaxAge(12 * 3600 * 1000);// milliseconds
return sessionManager;
}
所以查看DefaultWebSessionManager中源码:
/**
* Stores the Session's ID, usually as a Cookie, to associate with future requests.
*
* @param session the session that was just {@link #createSession created}.
*/
@Override
protected void onStart(Session session, SessionContext context) {
super.onStart(session, context);
if (!WebUtils.isHttp(context)) {
log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " +
"pair. No session ID cookie will be set.");
return;
}
HttpServletRequest request = WebUtils.getHttpRequest(context);
HttpServletResponse response = WebUtils.getHttpResponse(context);
if (isSessionIdCookieEnabled()) {
Serializable sessionId = session.getId();
storeSessionId(sessionId, request, response);
} else {
log.debug("Session ID cookie is disabled. No cookie has been set for new session with id {}", session.getId());
}
request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
}
追溯创建session的类型,发现onStart方法描述为调用的是createSession方法,追溯到DefaultSessionManager:
protected Session doCreateSession(SessionContext context) {
Session s = newSessionInstance(context);
if (log.isTraceEnabled()) {
log.trace("Creating session for host {}", s.getHost());
}
create(s);
return s;
}
通过newSessionInstance最后追溯到SimpleSessionFactory
public class SimpleSessionFactory implements SessionFactory {
/**
* Creates a new {@link SimpleSession SimpleSession} instance retaining the context's
* {@link SessionContext#getHost() host} if one can be found.
*
* @param initData the initialization data to be used during {@link Session} creation.
* @return a new {@link SimpleSession SimpleSession} instance
*/
public Session createSession(SessionContext initData) {
if (initData != null) {
String host = initData.getHost();
if (host != null) {
return new SimpleSession(host);
}
}
return new SimpleSession();
}
}
分析出来,当前系统中的默认采用的是SimpleSession类实现。