Shiro性能优化:解决Session频繁读写问题
背景
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频繁读写问题的更多相关文章
- SQL 查询性能优化----解决书签查找
先来看看什么是书签查找: 当优化器所选择的非聚簇索引只包含查询请求的一部分字段时,就需要一个查找(lookup)来检索其他字段来满足请求.对一个有聚簇索引的表来说是一个键查找(key lookup), ...
- JBoss 性能优化(解决Jboss内存紧张的问题)
修改$JBOSS_HOME/bin/run.conf文件 JAVA_OPTS="-Xms 520m -Xmx 1220m -Xss 15120k +XX:AggressiveHeap&q ...
- 万级K8s集群背后etcd稳定性及性能优化实践
背景与挑战 随着腾讯自研上云及公有云用户的迅速增长,一方面,腾讯云容器服务TKE服务数量和核数大幅增长, 另一方面我们提供的容器服务类型(TKE托管及独立集群.EKS弹性集群.edge边缘计算集群.m ...
- 万级K8s集群背后 etcd 稳定性及性能优化实践
1背景与挑战随着腾讯自研上云及公有云用户的迅速增长,一方面,腾讯云容器服务TKE服务数量和核数大幅增长, 另一方面我们提供的容器服务类型(TKE托管及独立集群.EKS弹性集群.edge边缘计算集群.m ...
- 万字长文详解HBase读写性能优化
一.HBase 读优化 1. HBase客户端优化 和大多数系统一样,客户端作为业务读写的入口,姿势使用不正确通常会导致本业务读延迟较高实际上存在一些使用姿势的推荐用法,这里一般需要关注四个问题: 1 ...
- ASP.NET性能优化之分布式Session
如果我们正在使用Session,那么构建高性能可扩展的ASP.NET网站,就必须解决分布式Session的架构,因为单服务器的SESSION处理能力会很快出现性能瓶颈,这类问题也被称之为Session ...
- 微擎开启性能优化里面的性能优化memcache内存优化及数据库读写分离
http://www.mitusky.com/forum.php?mod=viewthread&tid=3135 [微擎 安装使用] 微擎开启性能优化里面的性能优化memcache内存优化及数 ...
- Web性能优化 高并发网站解决 单例 已看1
Web性能优化分为服务器端和浏览器端两个方面. 一.浏览器端,关于浏览器端优化,分很多个方面1.压缩源码和图片JavaScript文件源代码可以采用混淆压缩的方式,CSS文件源代码进行普通压缩,JPG ...
- 推荐收藏系列:一文理解JVM虚拟机(内存、垃圾回收、性能优化)解决面试中遇到问题(图解版)
欢迎一起学习 <提升能力,涨薪可待篇> <面试知识,工作可待篇 > <实战演练,拒绝996篇 > 欢迎关注我博客 也欢迎关注公 众 号[Ccww笔记],原创技术文章 ...
随机推荐
- 极简 Node.js 入门 - 3.3 文件写入
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
- iptables初步接触
0.iptables命令选项输入顺序:iptables -t 表名 <-A/I/D/R> 规则链名 [规则号] <-i/o 网卡名> -p 协议名 <-s 源IP/源子网 ...
- 使用Spring Cloud Config统一管理配置,别再到处放配置文件了
1 前言 欢迎访问南瓜慢说 www.pkslow.com获取更多精彩文章! 可配置是一个成熟软件系统应该提供的特性,而配置管理对于大型系统就显得十分重要,特别是对于拥有多个应用的微服务系统.可喜的是, ...
- 结对项目 实现自动生成四则运算题目的程序 (C++)
本次作业由 陈余 与 郭奕材 结对完成 零.github地址: https://github.com/King-Authur/-Automatically-generate-four-arithmet ...
- 报错:ER_NO_DEFAULT_FOR_FIELD: Field 'status' doesn't have a default value
小白入门级错误,数据库插入数据时报错;ER_NO_DEFAULT_FOR_FIELD: Field 'status' doesn't have a default value 百度说是my.ini文 ...
- mysql再回首
Mysql与Oracle的区别 1.实例区别 Mysql是一个轻量型数据库,开源免费.Oracle是收费的而且价格非常高. Mysql一个实例可以操作多个库,而Oracle一个实例只能对应一个库. M ...
- maven文件合集
maven项目目录结构 聚合项目的pom.xml <?xml version="1.0" encoding="UTF-8"?> <projec ...
- Kafka Broker源码:网络层设计
一.整体架构 1.1 核心逻辑 1个Acceptor线程+N个Processor线程(network.threads)+M个Request Handle线程(io threads) 多线程多React ...
- Docker 部署 redis教程,附带部分小建议,防止踩坑
Docker 部署 redis,附带部分小建议,防止踩坑 跟所有人一样,我们先从docker基本命令开始 一.拉取redis镜像(配图来自菜鸟,其实截图没多大意义,对比看下) # 默认就拉取laste ...
- beautifulsoup教程
beautifulsoup教程 BeautifulSoup4是爬虫必学的技能.BeautifulSoup最主要的功能是从网页抓取数据,Beautiful Soup自动将输入文档转换为Unicode编码 ...