优化Shiro多次访问Redis的问题

在项目中我们可以使用redis进行shiro session的持久化和集群共享
但在实际项目中,用户进行一次登录操作,日志如下:

2018-04-25 14:30:20.075 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroSessionDao - 创建session:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.075 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 保存信息到缓存中:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.076 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroSessionDao - 更新session:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.076 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 保存信息到缓存中:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.077 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 从缓存中获取数据:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.078 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroSessionDao - 更新session:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.079 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 保存信息到缓存中:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.083 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 从缓存中获取数据:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.084 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 从缓存中获取数据:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.084 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 从缓存中获取数据:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.085 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 从缓存中获取数据:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.085 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 从缓存中获取数据:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.086 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroSessionDao - 更新session:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.086 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 保存信息到缓存中:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.087 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 从缓存中获取数据:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.087 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 从缓存中获取数据:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.088 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroSessionDao - 更新session:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.089 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 保存信息到缓存中:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.091 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 从缓存中获取数据:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3
2018-04-25 14:30:20.092 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 从缓存中获取数据:b0029f80-dbc5-40e6-8cbe-0dddaad69eb3

由于 Session 都持久化在 redis 中,并且我们使用了redis作为缓存服务器,导致 shiro 在请求处理中需要用到 session 的时候都要从 redis 中取数据并且反序列化。

这样的一次简单访问,访问redis的次数过多,查看DefaultSessionManager源码:

    // 获取session
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = 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;
        }

        // 获取session
        Session s = retrieveSessionFromDataSource(sessionId);
        if (s == null) {
            //session ID was provided, meaning one is expected to be found, but we couldn't find one:
            String msg = "Could not find session with ID [" + sessionId + "]";
            throw new UnknownSessionException(msg);
        }
        return s;
    }

    // 调用sessionDao,获取session
    protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
        return sessionDAO.readSession(sessionId);
    }

基本步骤:

  1. 获取sessionId
  2. 调用SessionDao的readSession获取session

分析:

  1. 这里的SessionDao一般是自定义的,使用redis持久化session
  2. 关键看retrieveSession方法,如何能减少单次请求内调用retrieveSessionFromDataSource的次数
  3. 可行性方法:获取session时将session缓存起来,需要注意session缓存的作用域和生命周期

突破点:

  1. 在 Web 下使用 shiro 时这个 sessionKey 是 WebSessionKey 类型的
  2. WebSessionKey这个类包含属性:ServletRequest

解决方案:

  1. 第一次获取到session后,把session对象放到request中
  2. 单次请求周期内获取session,直接从request中获取
  3. 请求结束后request被销毁

自定义SessionManager,重写retrieveSession方法,改变获取session的方法:

package com.ahut.shiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.WebSessionKey;

import javax.servlet.ServletRequest;
import java.io.Serializable;

/**
 * @author cheng
 * @className: RedisShiroSessionManager
 * @description: 自定义SessionManager
 * @dateTime 2018/4/25 14:41
 */
@Slf4j
public class RedisShiroSessionManager extends DefaultWebSessionManager {

    /**
     * @description: 获取session, 优化单次请求需要多次访问redis的问题
     * @author cheng
     * @dateTime 2018/4/25 14:43
     */
    @Override
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        // 获取sessionId
        Serializable sessionId = getSessionId(sessionKey);

        ServletRequest request = null;
        // 在 Web 下使用 shiro 时这个 sessionKey 是 WebSessionKey 类型的
        // 若是在web下使用,则获取request
        if (sessionKey instanceof WebSessionKey) {
            request = ((WebSessionKey) sessionKey).getServletRequest();
        }

        // 尝试从request中获取session
        if (request != null && sessionId != null) {
            Object sessionObj = request.getAttribute(sessionId.toString());
            if (sessionObj != null) {
                log.info("从request获取到session:{}", sessionId);
                return (Session) sessionObj;
            }
        }

        // 若从request中获取session失败,则从redis中获取session,并把获取到的session存储到request中方便下次获取
        Session session = super.retrieveSession(sessionKey);
        if (request != null && sessionId != null) {
            log.info("存储session到request中:{}", sessionId);
            request.setAttribute(sessionId.toString(), session);
        }

        return session;
    }
}

在ShiroConfig.java中配置自定义的SessionManager

    /**
     * @description: 自定义sessionManager
     * @author cheng
     * @dateTime 2018/4/24 10:37
     */
    public SessionManager createMySessionManager() {
        RedisShiroSessionManager sessionManager = new RedisShiroSessionManager();
        // 自定义sessionDao
        sessionManager.setSessionDAO(createRedisShiroSessionDao());
        // session的失效时长,单位是毫秒
        sessionManager.setGlobalSessionTimeout(ShiroUtil.GLOBAL_SESSION_TIMEOUT);
        // 删除失效的session
        sessionManager.setDeleteInvalidSessions(true);
        // 所有的session一定要将id设置到Cookie之中,需要提供有Cookie的操作模版
        sessionManager.setSessionIdCookie(createSessionIdCookie());
        // 定义sessionIdCookie模版可以进行操作的启用
        sessionManager.setSessionIdCookieEnabled(true);
        log.info("配置sessionManager");
        return sessionManager;
    }

    /**
     * @description: 注意方法返回值SecurityManager为org.apache.shiro.mgt.SecurityManager, 不要导错包
     * @author cheng
     * @dateTime 2018/4/18 15:48
     */
    public SecurityManager createSecurityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 自定义realm
        securityManager.setRealm(createMyRealm());
        // 自定义sessionManager
        securityManager.setSessionManager(createMySessionManager());
        // 自定义rememberMeManager
        securityManager.setRememberMeManager(createRememberMeManager());
        // 自定义cacheManager
        securityManager.setCacheManager(createCacheManager());
        log.info("配置rsecurityManager");
        return securityManager;
    }

优化后的日志访问记录

2018-04-25 14:56:16.843 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroSessionDao - 创建session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.843 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 保存信息到缓存中:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.843 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroSessionDao - 更新session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.844 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 保存信息到缓存中:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.845 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 从缓存中获取数据:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.846 ahut [http-nio-8080-exec-1] INFO  c.a.shiro.RedisShiroSessionManager - 存储sessionrequest:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.846 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroSessionDao - 更新session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.847 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 保存信息到缓存中:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.852 ahut [http-nio-8080-exec-1] INFO  c.a.shiro.RedisShiroSessionManager -request获取到session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.852 ahut [http-nio-8080-exec-1] INFO  c.a.shiro.RedisShiroSessionManager -request获取到session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.852 ahut [http-nio-8080-exec-1] INFO  c.a.shiro.RedisShiroSessionManager -request获取到session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.852 ahut [http-nio-8080-exec-1] INFO  c.a.shiro.RedisShiroSessionManager -request获取到session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.852 ahut [http-nio-8080-exec-1] INFO  c.a.shiro.RedisShiroSessionManager -request获取到session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.853 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroSessionDao - 更新session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.853 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 保存信息到缓存中:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.854 ahut [http-nio-8080-exec-1] INFO  c.a.shiro.RedisShiroSessionManager -request获取到session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.854 ahut [http-nio-8080-exec-1] INFO  c.a.shiro.RedisShiroSessionManager -request获取到session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.854 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroSessionDao - 更新session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.855 ahut [http-nio-8080-exec-1] INFO  com.ahut.shiro.RedisShiroCache - 保存信息到缓存中:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.856 ahut [http-nio-8080-exec-1] INFO  c.a.shiro.RedisShiroSessionManager -request获取到session:8475e279-8733-45d5-8fb7-08a371c4cd91
2018-04-25 14:56:16.857 ahut [http-nio-8080-exec-1] INFO  c.a.shiro.RedisShiroSessionManager -request获取到session:8475e279-8733-45d5-8fb7-08a371c4cd91

你可能感兴趣的:(Shiro)