redis session 超时时间_Shiro性能优化:解决Session频繁读写问题

本文介绍了在使用Shiro进行Session管理时,由于频繁访问Redis导致的性能问题。提出了本地缓存Session和避免不必要的Session更新两种优化策略,通过在请求级别缓存Session并重写SessionManager的touch()方法,减少对Redis的读写操作,提高系统性能。
摘要由CSDN通过智能技术生成

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达

  作者 |  张永恒

来源 |  urlify.cn/YjEZNj

背景

Shiro 提供了强大的 Session 管理功能,基于 Shiro 实现 Session 共享非常方便,只需要定制一个我们自己的SessionDAO,并将它绑定给 SessionManager 即可。在我们的 SessionDAO 中,通常会将 Session 保存到 Redis,那么 Shiro 对 Session 的增删改查,都会直接操作 Redis。

但是由于 Shiro 对 Session 的访问非常频繁,用户的一次请求,可能就会触发几十次的 Session 访问操作,在 Session 共享的场景下,如果每次都访问 Redis,势必会影响性能。

应对思路

本地缓存 Session

将 Session 对象缓存于本地内存中,能够有效减少从 Redis 中读取 Session 的次数。

最简单的方案,就是将 Session 对象保存到 request 域中,那么在一次请求内,只需要从 Redis 中获取一次,之后就可以直接从当前 request 域中获取,并且当请求结束后缓存会自动销毁,不用担心内存泄漏。

避免不必要的 Session 更新

ShiroFilter 对每个请求都会检查 Session 是否存在,如果存在,则调用 SessionManager 的 touch() 方法,将 Session 的 lastAccessTime 属性值更新为当前时间,并调用 SessionDAO 的 update() 方法保存更新。

由此可见,当 Session 被创建出来之后,用户的每个请求都会使 SessionDAO 的 update() 方法至少被调用一次。

那么 Session 的 lastAccessTime 属性是干嘛用的呢?有必要每个请求都去更新一下吗?

lastAccessTime 属性记录的是用户的上次访问时间,它主要用于验证 Session 是否超时,当用户访问系统时,如果本次访问的时间距离上次访问时间超过了 timeout 阈值,则判定 Session 超时。如果 lastAccessTime 的值不断更新,那么 Session 就有可能永不超时。因此,更新 lastAccessTime 属性值的操作可以认为是给 Session “续命”。

既然是“续命”,没必要每次都“续”(除非命真的很短)。我们可以重写 SessionManager 的 touch() 方法,在更新过 lastAccessTime 属性的值后,先不急着保存更新,而是计算一下两次访问的时间间隔,只有当它大于某个阈值时,才去主动调用 SessionDAO 的 update() 方法来保存更新。这样也就大大降低了 Session 更新的频率。

代码实现

ShiroSessionDAO.java
@Repository
public class ShiroSessionDAO extends AbstractSessionDAO {

    private static final String SESSION_REDIS_KEY_PREFIX = "session:";

    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        redisTemplate.boundValueOps(SESSION_REDIS_KEY_PREFIX + session.getId().toString()).set(session);return sessionId;
    }
    @Override
    public void update(Session session) throws UnknownSessionException {
        redisTemplate.boundValueOps(SESSION_REDIS_KEY_PREFIX + session.getId().toString()).set(session);
    }
    @Override
    public void delete(Session session) {
        redisTemplate.delete(SESSION_REDIS_KEY_PREFIX + session.getId().toString());
        HttpServletRequest request = getRequest();if (request != null) { // 一定要进行空值判断,因为SessionValidationScheduler的线程也会调用这个方法,而在那个线程中是不存在Request对象的
            request.removeAttribute(session.getId().toString());
        }
    }
    @Override
    protected Session doReadSession(Serializable sessionId) {
        HttpServletRequest request = getRequest();if (request != null) {
            Session sessionObj = (Session) request.getAttribute(sessionId.toString());if (sessionObj != null) {return sessionObj;
            }
        }
        Session session = (Session) redisTemplate.boundValueOps(SESSION_REDIS_KEY_PREFIX + sessionId).get();if (session != null && request != null) {
            request.setAttribute(sessionId.toString(), session);
        }return session;
    }
    @Override
    public Collection getActiveSessions() {
        Set keys = redisTemplate.keys(SESSION_REDIS_KEY_PREFIX + "*");if (keys != null && !keys.isEmpty()) {
            List sessions = redisTemplate.opsForValue().multiGet(keys);if (sessions != null) {return sessions.stream().map(o -> (Session) o).collect(Collectors.toList());
            }
        }return Collections.emptySet();
    }
    private HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();return requestAttributes != null ? requestAttributes.getRequest() : null;
    }
}
ShiroConfig.java
@Configuration
public class ShiroConfig {

    @Bean
    public SessionManager sessionManager(SessionDAO sessionDAO) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager() {
            @Override // 重写touch()方法,降低Session更新的频率
            public void touch(SessionKey key) throws InvalidSessionException {
                Session session = doGetSession(key);
                if (session != null) {
                    long oldTime = session.getLastAccessTime().getTime();
                    session.touch(); // 更新访问时间
                    long newTime = session.getLastAccessTime().getTime();
                    if (newTime - oldTime > 300000) { // 如果两次访问的时间间隔大于5分钟,主动持久化Session
                        onChange(session);
                    }
                }
            }
        };
        
        sessionManager.setSessionDAO(sessionDAO); // 绑定SessionDAO

        SimpleCookie sessionIdCookie = new SimpleCookie("sessionId");
        sessionIdCookie.setPath("/");
        sessionIdCookie.setMaxAge(8 * 60 * 60); // 单位:秒数
        sessionManager.setSessionIdCookie(sessionIdCookie); // 绑定Cookie模版
        
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setGlobalSessionTimeout(60 * 60 * 1000);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionValidationInterval(2 * 60 * 60 * 1000);
        sessionManager.setDeleteInvalidSessions(true);
        
        return sessionManager;
    }

    ... 略 ...

}
@Configuration
public class ShiroConfig {

    @Bean
    public SessionManager sessionManager(SessionDAO sessionDAO) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager() {
            @Override // 重写touch()方法,降低Session更新的频率
            public void touch(SessionKey key) throws InvalidSessionException {
                Session session = doGetSession(key);
                if (session != null) {
                    long oldTime = session.getLastAccessTime().getTime();
                    session.touch(); // 更新访问时间
                    long newTime = session.getLastAccessTime().getTime();
                    if (newTime - oldTime > 300000) { // 如果两次访问的时间间隔大于5分钟,主动持久化Session
                        onChange(session);
                    }
                }
            }
        };
        
        sessionManager.setSessionDAO(sessionDAO); // 绑定SessionDAO

        SimpleCookie sessionIdCookie = new SimpleCookie("sessionId");
        sessionIdCookie.setPath("/");
        sessionIdCookie.setMaxAge(8 * 60 * 60); // 单位:秒数
        sessionManager.setSessionIdCookie(sessionIdCookie); // 绑定Cookie模版
        
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setGlobalSessionTimeout(60 * 60 * 1000);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionValidationInterval(2 * 60 * 60 * 1000);
        sessionManager.setDeleteInvalidSessions(true);
        
        return sessionManager;
    }

    ... 略 ...

}

a5d5d6b5076ace372abc651b8cbd89c4.gif

2b601c1e06504aaaa68f79d03851e1fd.gif

  • 新款SpringBoot在线教育平台开源了

  • 精品帖子大汇总

  • 一把“乐观锁”轻松搞定高并发下的幂等性问题(附视频教程)

  • 一文搞懂Java8 Lambda表达式(附视频教程)

感谢点赞支持下哈 dc832a876ad2b77bdf5e6daaafa921d0.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值