本文共 9009 字,大约阅读时间需要 30 分钟。
原文地址,转载请注明出处: ©
该博客是接着上一篇博客: 请将两篇博客同时打开,方便查看。
关于shiro频繁访问redis,共分为两种,一种是频繁的去redis读取session , 一种是频繁的去更新redis 中的session,针对两种不同情况,分别写出解决方案。
上面的RedisSessionDAO类中依赖一个叫SessionInMemory的类,是shiro-redis作者为了解决一次请求频繁访问redis读取session的解决方案,基于本地cache,如果是在一秒内的请求,都会从本地cache中获取request。下面有更好的方案,但是这个代码我也没有删除。共存互不影响。
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。详细代码,请参考上篇博客。
参考博客:
关于频繁去Redis中读取Session有一个更好的解决方案,重写 DefaultWebSessionManager 的retrieveSession()
方法。在 Web 下使用 shiro 时这个 sessionKey 是 WebSessionKey 类型的,这个类有个我们很熟悉的属性:servletRequest。小伙伴们应该都灵光一现了!直接把 session 对象怼进 request 里去!那么在单次请求周期内我们都可以从 request 中取 session 了,而且请求结束后 request 被销毁,作用域和生命周期的问题都需要我们考虑了。 显然我们要 Override 这个retrieveSession方法,为此我们需要使用自定义的 SessionManager,如下: 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
参考博客:
由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法,更新的字段只有LastAccessTime(最后一次访问时间),由于会话失效是由Redis数据过期实现的,这个字段意义不大,为了减少对Redis的访问,降低网络压力,实现自己的Session,在SimpleSession上套一层,增加一个标识位,如果Session除lastAccessTime意外其它字段修改,就标识一下,只有标识为修改的才可以通过doUpdate访问Redis,否则直接返回。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
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。