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

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

优质文章,第一时间送达

  作者 |  张永恒

来源 |  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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/554351.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

360浏览器如何拦截和屏蔽网页广告

在浏览网页的时候总会弹出各种烦人的广告,用户可以使用浏览器设置,然后拦截掉网页的广告,操作起来非常的方便,用户可以使用浏览器马上学习起来,感兴趣的用户就一起来学习起来吧!马上就可以为用户提供最便捷的浏览方式&…

如何申请注销腾讯视频账号

今天小编给大家分享“如何申请注销腾讯视频账号”。腾讯视频在电视剧和电影播放过程中无广告,只要点击播放按钮,除了开头的广告外,视频畅快从头看到尾,当然现在有些电视剧直接把广告植入到剧中,哪怕你开了vip一样无法去…

mysql数据库表名大小写敏感_Mysql数据库名和表名的大小写敏感性问题

导读:一直以来,Mysql数据库名和表名的大小写都是个敏感的问题,困扰着Mysql数据库管理员。在 MySQL 中,数据库和表对应于那些目录下的目录和文件。因而,操作系统的敏感性决定数据库和表命名的大小写敏感。这就意味着数据…

手机优酷缓存的视频在哪找

很多人都喜爱在优酷视频上看剧或是影片,有时出门度假旅游或是乘火车等特殊情况,大家必须用手机优酷来下载好一些自身喜爱看的电视连续剧或是影片,便捷那时候没有网络,还可以在手机优酷上看电视剧,进而消磨无趣的時间。…

mysql触发器如果提示_mysql 触发器

1 引言Mysql的触发器和存储过程一样,都是嵌入到mysql的一段程序。触发器是mysql5新增的功能,目前线上凤巢系统、北斗系统以及哥伦布系统使用的数据库均是mysql5.0.45版本,很多程序比如fc-star管理端,sfrd(das),dorad…

PP视频怎么设置退出程序时清空本地播放记录

本文小编给大家分享PP视频怎么设置退出程序时清空本地播放记录,如果大家在使用PP视频过程当中遇到类似问题,欢迎阅读本文!大家都知道PP视频作为中国最大的视频分享网站,它有非常全面的视频资源。而我们只要通过下载这个客户端,就可…

python中属性是什么意思啊_python中的“对象属性”和一般属性是什么?

假设你有一个类Personclass Person:name "Samuel"age 50country "India"def method1(self):print("Method 1")print(dir(Person))上述程序的输出如下所示:^{pr2}$从上面的输出中可以看到,它返回该对象的有效属性的排序…

PP视频播放视频时如何关闭弹屏

本文小编给大家带来的是PP视频相关的内容。PP视频播放器官方下载电脑版始终以“用户体验”为生命,您可运行PP视频播放器,在线享受奇艺网站内全部免费高清正版视频。不管是哪个视频播放器都会有喜欢和不喜欢的用户,这是正常的情况,…

mysql too many connections_mysql too many connections 解决方法

1、mysql -u root -p 回车输入密码进入mysql2、show processlist;查看连接数,可以发现有很多连接处于sleep状态,这些其实是暂时没有用的,所以可以kill掉3、show variables like "max_connections";查看最大连接数,应该是…

qt更改类名_Qt编写自定义控件属性设计器

以前做.NET开发中,.NET直接就集成了属性设计器,VS不愧是宇宙第一IDE,你能够想到的都给你封装好了,用起来不要太爽!因为项目需要自从全面转Qt开发已经6年有余,在工业控制领域,有一些应用场景需要…

搜狐视频怎么开启自动连播

本文播放器家园网给大家整理了搜狐视频怎么开启自动连播方面的内容。搜狐视频支持在线进行加载,离线观看,没有网络一样看高清视频,别说你流量够多用不上,你在电梯里没信号,地铁网速慢。搜狐视频拥有独立的播放界面&…

mysql数据库更改文档_更改MySQL数据库目录位置

更改MySQL数据库目录位置MySQL默认的数据文档存储目录为/var/lib/mysql。假如要把MySQL目录移到/home/data下需要进行下面几步:1、home目录下建立data目录cd /homemkdir data2、把MySQL服务进程停掉:mysqladmin -u root -p shutdown3、把/var/lib/mysql整…

PP视频如何设置默认缓存个数

将来,PP视频将会在多元化的内容储备、个性化的产品体验、定制化营销服务领域继续发力,引领视频体验革命。不断提升连接人与服务的能力,更好的改变人们的生活。PP视频是一款可以电影资源非常丰富的播放器软件,用户可以在这里观看各…

腾讯视频怎么上传自己的视频?

腾讯视频,是定坐落于中国最大视频在线网络媒体,另外也是一款视频播放器。其以丰富多彩的內容、完美的收看感受、方便快捷的登陆方法、二十四小时多服务平台无缝拼接运用感受及其便捷共享的产品特性,满足客户需求在线播放视頻的要求。身旁有精…

sudo mysql压缩备份解压操作_高效管理文件之压缩及解压缩 .bz2 文件

对文件进行压缩,可以通过使用较少的字节对文件中的数据进行编码来显著地减小文件的大小,并且在跨网络的文件的备份和传送时很有用。 另一方面,解压文件意味着将文件中的数据恢复到初始状态。Linux 中有几个文件压缩和解压缩更具,比…

360浏览器自动刷新选项设置方法

360浏览器自动刷新选项设置方法 360浏览器不安装插件自动刷新怎么设置?我们使用浏览器在游览器贴吧的时候,经常会不同的按“CtrlR”或者是“F5”来刷新页面。网上有很多插件可以设置自动刷新页面,小编今天说的只需要在360浏览器设置一下就能实现这个功能…

zygoteinit.java_源码跟踪之启动流程:从ZygoteInit到onCreate

InstrumentationSDK版本名称: PieAPI Level: 28一、源码调用时序图1. Activity的启动流程说明:其中ActivityThread中执行的scheduleTransaction方法在其父类ClientTransactionHandler中,发送了ActivityThread.H.EXECUTE_TRANSACTION,Activity…

UC浏览器屏幕亮度在哪设置 UC浏览器屏幕亮度调节方法2019

UC浏览器是可以调节屏幕亮度的,大家可以根据自己白天和晚上的浏览习惯去设置,下面,我们来看看UC浏览器屏幕亮度调节方法,希望能够帮助到大家。 UC浏览器屏幕亮度在哪设置 打开手机,在手机桌面找到UC浏览器&#xff0…

java6虚拟机_Java 虚拟机之六:javap工具

一:简介javap是JDK自带的反汇编器,可以查看java编译器为我们生成的字节码。通过它,我们可以对照源代码和字节码,从而了解很多编译器内部的工作。javap命令的常用参数有:-l 打印行和本地变量表-public 只显示公共类和成…

搜狐视频app如何设置仅自己可看我的关注列表

播放器软件很多,本文播放器家园网小编给大家推荐搜狐视频。搜狐视频是当下非常流行常用的视频播放器,拥有安卓版、pc版、苹果版等适用于不同设备用户,拥有海量最新最热视频资源,精彩视频第一时间上新提醒,支持在线下载…