博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
解决Shiro频繁访问Redis读取和更新session(十二)
阅读量:3786 次
发布时间:2019-05-22

本文共 9009 字,大约阅读时间需要 30 分钟。

原文地址,转载请注明出处:      © 

该博客是接着上一篇博客: 请将两篇博客同时打开,方便查看。

前言

关于shiro频繁访问redis,共分为两种,一种是频繁的去redis读取session , 一种是频繁的去更新redis 中的session,针对两种不同情况,分别写出解决方案。

频繁读取session解决有两种方案:

第一种方案:本地缓存

上面的RedisSessionDAO类中依赖一个叫SessionInMemory的类,是shiro-redis作者为了解决一次请求频繁访问redis读取session的解决方案,基于本地cache,如果是在一秒内的请求,都会从本地cache中获取request。下面有更好的方案,但是这个代码我也没有删除。共存互不影响。

SessionInMemory.java
package com.springboot.test.shiro.config.shiro;import org.apache.shiro.session.Session;import java.util.Date;/** * Use ThreadLocal as a temporary storage of Session, so that shiro wouldn't keep read redis several times while a request coming. */public class SessionInMemory {
private Session session; private Date createTime; public Session getSession() { return session; } public void setSession(Session session) { this.session = session; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; }}

然后在RedisSessionDAO的doReadSession先去本地缓存查询是否有,有的话从本地获取,并对比时间,在规定时间内,则使用缓存中的session。详细代码,请参考上篇博客。

另一种方案:从request中获取

参考博客:

关于频繁去Redis中读取Session有一个更好的解决方案,重写
DefaultWebSessionManager 的 retrieveSession()方法。在 Web 下使用 shiro 时这个 sessionKey 是 WebSessionKey 类型的,这个类有个我们很熟悉的属性:servletRequest。小伙伴们应该都灵光一现了!直接把 session 对象怼进 request 里去!那么在单次请求周期内我们都可以从 request 中取 session 了,而且请求结束后 request 被销毁,作用域和生命周期的问题都需要我们考虑了。
显然我们要 Override 这个retrieveSession方法,为此我们需要使用自定义的 SessionManager,如下:

ShiroSessionManager.java
package com.springboot.test.shiro.config.shiro;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 org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.servlet.ServletRequest;import java.io.Serializable;/** * @author: wangsaichao * @date: 2018/6/23 * @description: 解决单次请求需要多次访问redis */public class ShiroSessionManager extends DefaultWebSessionManager {
private static Logger logger = LoggerFactory.getLogger(DefaultWebSessionManager.class); /** * 获取session * 优化单次请求需要多次访问redis的问题 * @param sessionKey * @return * @throws UnknownSessionException */ @Override protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException { Serializable sessionId = getSessionId(sessionKey); ServletRequest request = null; if (sessionKey instanceof WebSessionKey) { request = ((WebSessionKey) sessionKey).getServletRequest(); } if (request != null && null != sessionId) { Object sessionObj = request.getAttribute(sessionId.toString()); if (sessionObj != null) { logger.debug("read session from request"); return (Session) sessionObj; } } Session session = super.retrieveSession(sessionKey); if (request != null && null != sessionId) { request.setAttribute(sessionId.toString(), session); } return session; }}

记得在ShiroConfig中配置SessionManager为自定义的ShiroSessionManager

解决频繁更新session解决方案

参考博客:

由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法,更新的字段只有LastAccessTime(最后一次访问时间),由于会话失效是由Redis数据过期实现的,这个字段意义不大,为了减少对Redis的访问,降低网络压力,实现自己的Session,在SimpleSession上套一层,增加一个标识位,如果Session除lastAccessTime意外其它字段修改,就标识一下,只有标识为修改的才可以通过doUpdate访问Redis,否则直接返回。

ShiroSession.java
package com.springboot.test.shiro.config.shiro;import org.apache.shiro.session.mgt.SimpleSession;import java.io.Serializable;import java.util.Date;import java.util.Map;/** * @author: wangsaichao * @date: 2018/6/23 * @description: 由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法, * 增加标识位,如果只是更新lastAccessTime SessionDao update方法直接返回 */public class ShiroSession extends SimpleSession implements Serializable {
// 除lastAccessTime以外其他字段发生改变时为true private boolean isChanged = false; public ShiroSession() { super(); this.setChanged(true); } public ShiroSession(String host) { super(host); this.setChanged(true); } @Override public void setId(Serializable id) { super.setId(id); this.setChanged(true); } @Override public void setStopTimestamp(Date stopTimestamp) { super.setStopTimestamp(stopTimestamp); this.setChanged(true); } @Override public void setExpired(boolean expired) { super.setExpired(expired); this.setChanged(true); } @Override public void setTimeout(long timeout) { super.setTimeout(timeout); this.setChanged(true); } @Override public void setHost(String host) { super.setHost(host); this.setChanged(true); } @Override public void setAttributes(Map
attributes) { super.setAttributes(attributes); this.setChanged(true); } @Override public void setAttribute(Object key, Object value) { super.setAttribute(key, value); this.setChanged(true); } @Override public Object removeAttribute(Object key) { this.setChanged(true); return super.removeAttribute(key); } /** * 停止 */ @Override public void stop() { super.stop(); this.setChanged(true); } /** * 设置过期 */ @Override protected void expire() { this.stop(); this.setExpired(true); } public boolean isChanged() { return isChanged; } public void setChanged(boolean isChanged) { this.isChanged = isChanged; } @Override public boolean equals(Object obj) { return super.equals(obj); } @Override protected boolean onEquals(SimpleSession ss) { return super.onEquals(ss); } @Override public int hashCode() { return super.hashCode(); } @Override public String toString() { return super.toString(); }}
ShiroSessionFactory.java
package com.springboot.test.shiro.config.shiro;import org.apache.commons.lang3.StringUtils;import org.apache.shiro.session.Session;import org.apache.shiro.session.mgt.SessionContext;import org.apache.shiro.session.mgt.SessionFactory;import org.apache.shiro.web.session.mgt.DefaultWebSessionContext;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.servlet.http.HttpServletRequest;/** * @author: wangsaichao * @date: 2018/6/23 * @description: */public class ShiroSessionFactory implements SessionFactory {
private static final Logger logger = LoggerFactory.getLogger(ShiroSessionFactory.class); @Override public Session createSession(SessionContext initData) { ShiroSession session = new ShiroSession(); HttpServletRequest request = (HttpServletRequest)initData.get(DefaultWebSessionContext.class.getName() + ".SERVLET_REQUEST"); session.setHost(getIpAddress(request)); return session; } public static String getIpAddress(HttpServletRequest request) { String localIP = "127.0.0.1"; String ip = request.getHeader("x-forwarded-for"); if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; }}

然后在ShiroConfig配置该Bean,并赋值给sessionManager

@Bean("sessionFactory")public ShiroSessionFactory sessionFactory(){    ShiroSessionFactory sessionFactory = new ShiroSessionFactory();    return sessionFactory;}@Bean("sessionManager")public SessionManager sessionManager() {    ShiroSessionManager sessionManager = new ShiroSessionManager();     ... 该出省略其他配置    sessionManager.setSessionFactory(sessionFactory());    return sessionManager;}

然后在RedisSessionDAO的update方法上判断如果只是更改session的lastAccessTime,则直接返回。

@Overridepublic void update(Session session) throws UnknownSessionException {    //如果会话过期/停止 没必要再更新了    try {        if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {            return;        }        if (session instanceof ShiroSession) {            // 如果没有主要字段(除lastAccessTime以外其他字段)发生改变            ShiroSession ss = (ShiroSession) session;            if (!ss.isChanged()) {                return;            }            //如果没有返回 证明有调用 setAttribute往redis 放的时候永远设置为false            ss.setChanged(false);        }        this.saveSession(session);    } catch (Exception e) {        logger.warn("update Session is failed", e);    }}

这里注意:在操作redis更新session时候, changed属性一定是false, 原博客中并没有说明,我在测试的时候,发现如果只是更改lastAccessTime也不会直接返回,因为从redis拿出来的是true 。所以,既然走到往redis更新session这一步,那一定有setAttributes等方法被调用.所以往redis放的时候设置为false。下次从redis获取session是false 只是更改lastAccessTime 那么 changed属性就是false,将不会操作redis。

你可能感兴趣的文章
三种设计模式
查看>>
牛客编程题(八)
查看>>
牛客编程题(九)
查看>>
过滤流
查看>>
3.输入整型数组和排序标识,对其元素按照升序或降序进行排序
查看>>
13.找到字符串的最长无重复字符串字串
查看>>
java常用垃圾回收器G1和CMS有什么区别
查看>>
BIO、NIO,AIO的区别
查看>>
linux压缩与解压
查看>>
数据结构基础(一)
查看>>
Linux反弹shell姿势总结
查看>>
CVE-2018-2894 WebLogic远程上传漏洞复现
查看>>
Nginx解析漏洞复现
查看>>
GhostScript沙箱绕过(命令执行漏洞)CVE-2018-16509
查看>>
通过图片获取地理位置
查看>>
PHP提权姿势
查看>>
Linux VI VIM编辑器
查看>>
Linux 进程管理
查看>>
Vulmap的使用
查看>>
SPSS Modeler工具笔记
查看>>