背景

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<String, Object> 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<Session> getActiveSessions() {
Set<String> keys = redisTemplate.keys(SESSION_REDIS_KEY_PREFIX + "*");
if (keys != null && !keys.isEmpty()) {
List<Object> 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;
} ... 略 ... }

Shiro性能优化:解决Session频繁读写问题的更多相关文章

  1. SQL 查询性能优化----解决书签查找

    先来看看什么是书签查找: 当优化器所选择的非聚簇索引只包含查询请求的一部分字段时,就需要一个查找(lookup)来检索其他字段来满足请求.对一个有聚簇索引的表来说是一个键查找(key lookup), ...

  2. JBoss 性能优化(解决Jboss内存紧张的问题)

    修改$JBOSS_HOME/bin/run.conf文件   JAVA_OPTS="-Xms 520m -Xmx 1220m -Xss 15120k +XX:AggressiveHeap&q ...

  3. 万级K8s集群背后etcd稳定性及性能优化实践

    背景与挑战 随着腾讯自研上云及公有云用户的迅速增长,一方面,腾讯云容器服务TKE服务数量和核数大幅增长, 另一方面我们提供的容器服务类型(TKE托管及独立集群.EKS弹性集群.edge边缘计算集群.m ...

  4. 万级K8s集群背后 etcd 稳定性及性能优化实践

    1背景与挑战随着腾讯自研上云及公有云用户的迅速增长,一方面,腾讯云容器服务TKE服务数量和核数大幅增长, 另一方面我们提供的容器服务类型(TKE托管及独立集群.EKS弹性集群.edge边缘计算集群.m ...

  5. 万字长文详解HBase读写性能优化

    一.HBase 读优化 1. HBase客户端优化 和大多数系统一样,客户端作为业务读写的入口,姿势使用不正确通常会导致本业务读延迟较高实际上存在一些使用姿势的推荐用法,这里一般需要关注四个问题: 1 ...

  6. ASP.NET性能优化之分布式Session

    如果我们正在使用Session,那么构建高性能可扩展的ASP.NET网站,就必须解决分布式Session的架构,因为单服务器的SESSION处理能力会很快出现性能瓶颈,这类问题也被称之为Session ...

  7. 微擎开启性能优化里面的性能优化memcache内存优化及数据库读写分离

    http://www.mitusky.com/forum.php?mod=viewthread&tid=3135 [微擎 安装使用] 微擎开启性能优化里面的性能优化memcache内存优化及数 ...

  8. Web性能优化 高并发网站解决 单例 已看1

    Web性能优化分为服务器端和浏览器端两个方面. 一.浏览器端,关于浏览器端优化,分很多个方面1.压缩源码和图片JavaScript文件源代码可以采用混淆压缩的方式,CSS文件源代码进行普通压缩,JPG ...

  9. 推荐收藏系列:一文理解JVM虚拟机(内存、垃圾回收、性能优化)解决面试中遇到问题(图解版)

    欢迎一起学习 <提升能力,涨薪可待篇> <面试知识,工作可待篇 > <实战演练,拒绝996篇 > 欢迎关注我博客 也欢迎关注公 众 号[Ccww笔记],原创技术文章 ...

随机推荐

  1. ceph 快照,克隆

    转载 https://my.oschina.net/wangzilong/blog/1595081 ceph 快照,克隆 ceph是一个非常好的后端存储系统.其中包括最常用的块存储,对象存储,文件系统 ...

  2. Linux基本命令及编程环境实验

    目录 一.Linux基本命令详细汇总 1.目录及文件相关命令 2.系统信息查询 3.文件操作(统计.过滤.搜索.权限) 4.其他命令 二.Linux终端上vi命令编程 1.进入vi命令模式 2.vi编 ...

  3. HTTP/3 来了,你了解它么?

    作为我们网上冲浪最为常见,也经常被人忽视的 HTTP 已经更新换代到了 HTTP/3.本文简单明了的带你认识 HTTP/3 的作用. 最近二狗子看到自己存储女神婷婷照片所用的云服务商--又拍云推出了 ...

  4. SpringCloud系列之Nacos应用篇

    前言 原先项目是以SpringConfig作为项目配置中心组件,Eureka作为服务注册发现组件,基本上就是SpringCloud全家桶,Eureka已经停更,所以前期调研可替换方案,主流替换方案有C ...

  5. Entity Framework 6 实体某些字段根据模型状态进行自动更新内容

    1.定义基础实体对象 public class BaseEntity { public int Id { get; set; } public DateTime? CreateTime { get; ...

  6. Python3网络爬虫之requests动态爬虫:拉钩网

    操作环境: Windows10.Python3.6.Pycharm.谷歌浏览器目标网址: https://www.lagou.com/jobs/list_Python/p-city_0?px=defa ...

  7. Spine学习九 - 冰冻效果

    想象这样一个效果,一个人被冰霜攻击命中,然后这个人整个就被冰冻了,那么spine动画要如何实现这个效果呢? 1.首先需要一个Spine动画,这个动画应该是相对静止的,因为人物已经被冰冻了,那么这个人儿 ...

  8. Azure Storage 系列(二) .NET Core Web 项目中操作 Blob 存储

    一,引言 上一篇文章,我们介绍到在实际项目中系统会产生大量的日志文件,用户上传的头像等等,同时也介绍到可以使用Azure Blob Storage 来存储项目中的一些日志文件,用户头像,用户视频等等. ...

  9. SpringCloud入门 消息总线 Bus

    消息总线 1.概述 使用SpringCloud Bus配和Spring Cloud Config使用实现配置的动态刷新 Bus只支持消息处理:RabbitMQ和Kafaka. 能干嘛 能管理和传播分布 ...

  10. 解决Oracle在win10 64使用plsql 无法显示表

    将当前用户切换至所有用户,然后再切换至当前用户,问题解决 图中sql也可查询出当前库中存在的表: select object_name from user_objects where lower(ob ...