优化 shiro 多次调用 redis 的问题

(46) 2024-05-07 17:01:02

我们常使用 Shiro + redis 的组合解决集群下的 Session 共享问题,这里就不展开如何集成的问题了。
在进行日常优化的过程中,我通过日志发现这么一段日志:

2017-09-17 15:16:07.723 -DEBUG [nio-8080-exec-6] org.apache.shiro.session.mgt.DefaultSessionManager           : Creating new EIS record for new session instance [org.apache.shiro.session.mgt.SimpleSession,id=null]
2017-09-17 15:16:07.723 -DEBUG [nio-8080-exec-6] cn.bqjr.eily.shiro.spring.boot.RedisShiroSessionDAO          : 创建session:74a8d2b3-7143-40f1-b7bb-a9596975bc90
2017-09-17 15:16:07.723 -DEBUG [nio-8080-exec-6] cn.bqjr.eily.shiro.spring.boot.RedisShiroSessionDAO          : 获取session:74a8d2b3-7143-40f1-b7bb-a9596975bc90
2017-09-17 15:16:07.723 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Opening RedisConnection
2017-09-17 15:16:07.723 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Closing Redis Connection
2017-09-17 15:16:07.723 -DEBUG [nio-8080-exec-6] cn.bqjr.eily.shiro.spring.boot.RedisShiroSessionDAO          : 保存session:74a8d2b3-7143-40f1-b7bb-a9596975bc90
2017-09-17 15:16:07.724 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Opening RedisConnection
2017-09-17 15:16:07.724 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Closing Redis Connection
2017-09-17 15:16:07.725 -DEBUG [nio-8080-exec-6] cn.bqjr.eily.shiro.spring.boot.RedisShiroSessionDAO          : 获取session:74a8d2b3-7143-40f1-b7bb-a9596975bc90
2017-09-17 15:16:07.725 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Opening RedisConnection
2017-09-17 15:16:07.725 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Closing Redis Connection
2017-09-17 15:16:07.725 -DEBUG [nio-8080-exec-6] cn.bqjr.eily.shiro.spring.boot.RedisShiroSessionDAO          : 保存session:74a8d2b3-7143-40f1-b7bb-a9596975bc90
2017-09-17 15:16:07.725 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Opening RedisConnection
2017-09-17 15:16:07.725 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Closing Redis Connection
2017-09-17 15:16:07.725 -DEBUG [nio-8080-exec-6] org.apache.shiro.web.session.mgt.DefaultWebSessionManager    : Session ID cookie is disabled.  No cookie has been set for new session with id 74a8d2b3-7143-40f1-b7bb-a9596975bc90
2017-09-17 15:16:07.725 -DEBUG [nio-8080-exec-6] cn.bqjr.eily.shiro.spring.boot.RedisShiroSessionDAO          : 获取session:74a8d2b3-7143-40f1-b7bb-a9596975bc90
2017-09-17 15:16:07.725 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Opening RedisConnection
2017-09-17 15:16:07.726 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Closing Redis Connection
2017-09-17 15:16:07.726 -DEBUG [nio-8080-exec-6] cn.bqjr.eily.shiro.spring.boot.RedisShiroSessionDAO          : 保存session:74a8d2b3-7143-40f1-b7bb-a9596975bc90
2017-09-17 15:16:07.726 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Opening RedisConnection
2017-09-17 15:16:07.727 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Closing Redis Connection
2017-09-17 15:16:07.727 -DEBUG [nio-8080-exec-6] cn.bqjr.eily.shiro.spring.boot.RedisShiroSessionDAO          : 获取session:74a8d2b3-7143-40f1-b7bb-a9596975bc90
2017-09-17 15:16:07.727 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Opening RedisConnection
2017-09-17 15:16:07.727 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Closing Redis Connection
2017-09-17 15:16:07.727 -DEBUG [nio-8080-exec-6] cn.bqjr.eily.shiro.spring.boot.RedisShiroSessionDAO          : 保存session:74a8d2b3-7143-40f1-b7bb-a9596975bc90
2017-09-17 15:16:07.728 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Opening RedisConnection
2017-09-17 15:16:07.728 -DEBUG [nio-8080-exec-6] org.springframework.data.redis.core.RedisConnectionUtils     : Closing Redis Connection

值得注意的是,这一切是在单次请求中发生的!由于 Session 都持久化在 redis 中,导致 shiro 在请求处理中需要用到 session 的时候都要从 redis 中取数据并且反序列化,虽然 redis 的存取性能爆表,但是在如此场景中明显是 “铺张浪费” 了。
我们一步步来,先看看能不能减少读取 session 的次数,跟踪源码定位 shiro 获取 session 的方法:

// DefaultSessionManager.class

protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
    Serializable sessionId = this.getSessionId(sessionKey);
    if (sessionId == null) {
        log.debug("Unable to resolve session ID from SessionKey [{}].  Returning null to indicate a session could not be found.", sessionKey);
        return null;
    } else {
        Session s = this.retrieveSessionFromDataSource(sessionId);
        if (s == null) {
            String msg = "Could not find session with ID [" + sessionId + "]";
            throw new UnknownSessionException(msg);
        } else {
            return s;
        }
    }
}

protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
    return this.sessionDAO.readSession(sessionId);
}

这里可以直观地看到,shiro 取得 SessionId 后直接通过 sessionDao 去获取 Session 对象了,由于我们集成了 redis,所以这里的 dao 一般是我们自定义的 RedisSessionDao 了,这就不多展开了。关键看retrieveSession方法,如何能减少单次请求内调用retrieveSessionFromDataSource的次数呢?我们惯性思维会想到首次获取到 session 后就将其缓存起来,下次再获取时就直接返回而避免再次调用 redis。这种方式需要注意的就是这个 session 的作用域问题,要避免出现不同请求间 session 对象的隔离。要实现的方法很多,比如 ThreadLocal,但是使用它咱就要考虑数据生命周期或者作用域的问题了,有没有别的更简单点的方式?

答案是必须的,在对retrieveSession方法进行 debug 的时候,我发现 sessionKey 变量的有趣之处:
优化 shiro 多次调用 redis 的问题 (https://mushiming.com/)  第1张在 Web 下使用 shiro 时这个 sessionKey 是 WebSessionKey 类型的,这个类有个我们很熟悉的属性:servletRequest。小伙伴们应该都灵光一现了!直接把 session 对象怼进 request 里去!那么在单次请求周期内我们都可以从 request 中取 session 了,而且请求结束后 request 被销毁,作用域和生命周期的问题都不需要我们考虑了。
显然我们要 Override 这个retrieveSession方法,为此我们需要使用自定义的 SessionManager,如下:

public class ShiroSessionManager extends DefaultWebSessionManager {
    /**
     * 获取session
     * 优化单次请求需要多次访问redis的问题
     * @param sessionKey
     * @return
     * @throws UnknownSessionException
     */
    @Override
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = getSessionId(sessionKey);

        ServletRequest request = null;
        if (sessionKey instanceof WebSessionKey) {
            request = ((WebSessionKey) sessionKey).getServletRequest();
        }

        if (request != null && null != sessionId) {
            Object sessionObj = request.getAttribute(sessionId.toString());
            if (sessionObj != null) {
                return (Session) sessionObj;
            }
        }

        Session session = super.retrieveSession(sessionKey);
        if (request != null && null != sessionId) {
            request.setAttribute(sessionId.toString(), session);
        }
        return session;
    }
}

剩下的就是在 ShiroConfiguration 中注册这个自定义 SessionManager 即可。

参考地址:http://www.hillfly.com/2017/182.html

l

THE END

发表回复