java shiro 访问频率_shiro之redis频繁访问问题

本文探讨了使用Shiro进行分布式session共享时遇到的Redis高并发问题,提出了两种解决方案:1) 通过重写sessionManager的retrieveSession方法,从request中先尝试获取session,降低对Redis的访问;2) 在本地内存中缓存session,当session不存在时再从Redis读取,确保缓存存活时间短于Redis中session的存活时间,以减少并发访问。
摘要由CSDN通过智能技术生成

目前安全框架shiro使用较为广泛,其功能也比较强大。为了分布式session共享,通常的做法是将session存储在redis中,实现多个节点获取同一个session。此实现可以实现session共享,但session的特点是内存存储,就是为了高速频繁访问,每个请求都必须验证session是否存在是否过期,也从session中获取数据。这样导致一个页面刷新过程中的数十个请求会同时访问redis,在几毫秒内同时操作session的获取,修改,更新,保存,删除等操作,从而造成redis的并发量飙升,刷新一个页面操作redis几十到几百次。

为了解决由于session共享造成的redis高并发问题,很明显需要在redis之前做一次短暂的session缓存,如果该缓存存在就不用从redis中获取,从而减少同时访问redis的次数。如果做session缓存,主要有两种种方案,其实原理都相同:

1>重写sessionManager的retrieveSession方法。首先从request中获取session,如果request中不存在再走原来的从redis中获取。这样可以让一个请求的多次访问redis问题得到解决,因为request的生命周期为浏览器发送一个请求到接收服务器的一次响应完成,因此,在一次请求中,request中的session是一直存在的,并且不用担心session超时过期等的问题。这样就可以达到有多少次请求就几乎有多少次访问redis,大大减少单次请求,频繁访问redis的问题。大大减少redis的并发数量。此实现方法最为简单。

1 packagecn.uce.web.login.filter;2

3 importjava.io.Serializable;4

5 importjavax.servlet.ServletRequest;6

7 importorg.apache.shiro.session.Session;8 importorg.apache.shiro.session.UnknownSessionException;9 importorg.apache.shiro.session.mgt.SessionKey;10 importorg.apache.shiro.web.session.mgt.DefaultWebSessionManager;11 importorg.apache.shiro.web.session.mgt.WebSessionKey;12

13 public class ShiroSessionManager extendsDefaultWebSessionManager {14 /**

15 * 获取session16 * 优化单次请求需要多次访问redis的问题17 *@paramsessionKey18 *@return

19 *@throwsUnknownSessionException20 */

21 @Override22 protected Session retrieveSession(SessionKey sessionKey) throwsUnknownSessionException {23 Serializable sessionId =getSessionId(sessionKey);24

25 ServletRequest request = null;26 if (sessionKey instanceofWebSessionKey) {27 request =((WebSessionKey) sessionKey).getServletRequest();28 }29

30 if (request != null && null !=sessionId) {31 Object sessionObj =request.getAttribute(sessionId.toString());32 if (sessionObj != null) {33 return(Session) sessionObj;34 }35 }36

37 Session session = super.retrieveSession(sessionKey);38 if (request != null && null !=sessionId) {39 request.setAttribute(sessionId.toString(), session);40 }41 returnsession;42 }43 }

2>session缓存于本地内存中。自定义cacheRedisSessionDao,该sessionDao中一方面注入cacheManager用于session缓存,另一方面注入redisManager用于session存储,当createSession和updateSession直接使用redisManager操作redis.保存session.当readSession先用cacheManager从cache中读取,如果不存在再用redisManager从redis中读取。注意:该方法最大的特点是session缓存的存活时间必须小于redis中session的存活时间,就是当redus的session死亡,cahe中的session一定死亡,为了保证这一特点,cache中的session的存活时间应该设置为s级,设置为1s比较合适,并且存活时间固定不能刷新,不能随着访问而延长存活。

/****/

packagecom.uc56.web.omg.authentication;importjava.io.Serializable;importjava.util.Collection;importjava.util.Date;importjava.util.HashSet;importjava.util.Set;importorg.apache.shiro.session.ExpiredSessionException;importorg.apache.shiro.session.Session;importorg.apache.shiro.session.UnknownSessionException;importorg.apache.shiro.session.mgt.ValidatingSession;importorg.apache.shiro.session.mgt.eis.CachingSessionDAO;importorg.apache.shiro.subject.support.DefaultSubjectContext;importorg.crazycake.shiro.SerializeUtils;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importcom.uc56.web.omg.shiroredis.CustomRedisManager;/*** 将从redis读取的session进行本地缓存,本地缓存失效时重新从redis读取并更新最后访问时间,解决shiro频繁读取redis问题*/

public class CachingShiroSessionDao extendsCachingSessionDAO {private static final Logger logger = LoggerFactory.getLogger(CachingShiroSessionDao.class);/**保存到Redis中key的前缀*/

private String keyPrefix = "";/*** jedis 操作redis的封装*/

privateCustomRedisManager redisManager;/*** 如DefaultSessionManager在创建完session后会调用该方法;

* 如保存到关系数据库/文件系统/NoSQL数据库;即可以实现会话的持久化;

* 返回会话ID;主要此处返回的ID.equals(session.getId());*/@OverrideprotectedSerializable doCreate(Session session) {//创建一个Id并设置给Session

Serializable sessionId = this.generateSessionId(session);

assignSessionId(session, sessionId);this.saveSession(session);returnsessionId;

}/*** 重写CachingSessionDAO中readSession方法,如果Session中没有登陆信息就调用doReadSession方法从Redis中重读*/@Overridepublic Session readSession(Serializable sessionId) throwsUnknownSessionException {

Session session=getCachedSession(sessionId);if (session == null

|| session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {

session= this.doReadSession(sessionId);if (session == null) {throw new UnknownSessionException("There is no session with id [" + sessionId + "]");

}else{//缓存

cache(session, session.getId());

}

}returnsession;

}/*** 根据会话ID获取会话

*

*@paramsessionId 会话ID

*@return

*/@OverrideprotectedSession doReadSession(Serializable sessionId) {

ShiroSession shiroSession= null;try{

shiroSession= (ShiroSession)SerializeUtils.deserialize(redisManager.get(this.getByteKey(sessionId)));if (shiroSession != null

&& shiroSession.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) != null) {//检查session是否过期

shiroSession.validate();//重置Redis中Session的最后访问时间

shiroSession.setLastAccessTime(newDate());this.saveSession(shiroSession);

logger.info("sessionId {} name {} 被读取并更新访问时间", sessionId, shiroSession.getClass().getName());

}

}catch(Exception e) {if (!(e instanceofExpiredSessionException)) {

logger.warn("读取Session失败", e);

}else{

logger.warn("session已失效:{}", e.getMessage());

}

}returnshiroSession;

}//扩展更新缓存机制,每次请求不重新更新session,更新session会延长session的失效时间

@Overridepublic void update(Session session) throwsUnknownSessionException {

doUpdate(session);if (session instanceofValidatingSession) {if(((ValidatingSession) session).isValid()) {//不更新ehcach中的session,使它在设定的时间内过期//cache(session, session.getId());

} else{

uncache(session);

}

}else{

cache(session, session.getId());

}

}/*** 更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用*/@Overrideprotected voiddoUpdate(Session session) {//如果会话过期/停止 没必要再更新了

try{if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {return;

}

}catch(Exception e) {

logger.error("ValidatingSession error");

}try{if (session instanceofShiroSession) {//如果没有主要字段(除lastAccessTime以外其他字段)发生改变

ShiroSession shiroSession =(ShiroSession) session;if (!shiroSession.isChanged()) {return;

}

shiroSession.setChanged(false);this.saveSession(session);

logger.info("sessionId {} name {} 被更新", session.getId(), session.getClass().getName());

}else if (session instanceofSerializable) {this.saveSession(session);

logger.info("sessionId {} name {} 作为非ShiroSession对象被更新, ", session.getId(), session.getClass().getName());

}else{

logger.warn("sessionId {} name {} 不能被序列化 更新失败", session.getId(), session.getClass().getName());

}

}catch(Exception e) {

logger.warn("更新Session失败", e);

}

}/*** 删除会话;当会话过期/会话停止(如用户退出时)会调用*/@Overrideprotected voiddoDelete(Session session) {try{

redisManager.del(this.getByteKey(session.getId()));

logger.debug("Session {} 被删除", session.getId());

}catch(Exception e) {

logger.warn("修改Session失败", e);

}

}/*** 删除cache中缓存的Session*/

public voiduncache(Serializable sessionId) {

Session session= this.readSession(sessionId);super.uncache(session);

logger.info("取消session {} 的缓存", sessionId);

}/***

* 统计当前活动的session*/@Overridepublic CollectiongetActiveSessions() {

Set sessions = new HashSet();

Set keys = redisManager.keys(this.keyPrefix + "*");if(keys != null && keys.size()>0){for(byte[] key:keys){

Session s=(Session)SerializeUtils.deserialize(redisManager.get(key));

sessions.add(s);

}

}returnsessions;

}/*** save session

*@paramsession

*@throwsUnknownSessionException*/

private void saveSession(Session session) throwsUnknownSessionException{if(session == null || session.getId() == null){

logger.error("session or session id is null");return;

}byte[] key =getByteKey(session.getId());byte[] value =SerializeUtils.serialize(session);

session.setTimeout(redisManager.getExpire()* 1L);this.redisManager.set(key, value, redisManager.getExpire());

}/*** 将key转换为byte[]

*@paramkey

*@return

*/

private byte[] getByteKey(Serializable sessionId){

String preKey= this.keyPrefix +sessionId;returnpreKey.getBytes();

}publicCustomRedisManager getRedisManager() {returnredisManager;

}public voidsetRedisManager(CustomRedisManager redisManager) {this.redisManager =redisManager;/*** 初使化RedisManager*/

this.redisManager.init();

}/*** 获取 保存到Redis中key的前缀

*@returnkeyPrefix*/

publicString getKeyPrefix() {returnkeyPrefix;

}/*** 设置 保存到Redis中key的前缀

*@paramkeyPrefix 保存到Redis中key的前缀*/

public voidsetKeyPrefix(String keyPrefix) {this.keyPrefix =keyPrefix;

}

}

/****/

packagecom.uc56.web.omg.authentication;importjava.io.Serializable;importjava.util.Date;importjava.util.Map;importorg.apache.shiro.session.mgt.SimpleSession;/*** 由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法,

* 增加标识位,如果只是更新lastAccessTime SessionDao update方法直接返回*/

public class ShiroSession extends SimpleSession implementsSerializable {/****/

private static final long serialVersionUID = 1L;//除lastAccessTime以外其他字段发生改变时为true

private booleanisChanged;publicShiroSession() {super();this.setChanged(true);

}publicShiroSession(String host) {super(host);this.setChanged(true);

}

@Overridepublic voidsetId(Serializable id) {super.setId(id);this.setChanged(true);

}

@Overridepublic voidsetStopTimestamp(Date stopTimestamp) {super.setStopTimestamp(stopTimestamp);this.setChanged(true);

}

@Overridepublic void setExpired(booleanexpired) {super.setExpired(expired);this.setChanged(true);

}

@Overridepublic void setTimeout(longtimeout) {super.setTimeout(timeout);this.setChanged(true);

}

@Overridepublic voidsetHost(String host) {super.setHost(host);this.setChanged(true);

}

@Overridepublic void setAttributes(Mapattributes) {super.setAttributes(attributes);this.setChanged(true);

}

@Overridepublic voidsetAttribute(Object key, Object value) {super.setAttribute(key, value);this.setChanged(true);

}

@OverridepublicObject removeAttribute(Object key) {this.setChanged(true);return super.removeAttribute(key);

}//更新最后访问时间不更新redis

@Overridepublic voidtouch() {this.setChanged(false);super.touch();

}/*** 停止*/@Overridepublic voidstop() {super.stop();this.setChanged(true);

}/*** 设置过期*/@Overrideprotected voidexpire() {this.stop();this.setExpired(true);

}public booleanisChanged() {returnisChanged;

}public void setChanged(booleanisChanged) {this.isChanged =isChanged;

}

@Overridepublic booleanequals(Object obj) {return super.equals(obj);

}

@Overrideprotected booleanonEquals(SimpleSession ss) {return super.onEquals(ss);

}

@Overridepublic inthashCode() {return super.hashCode();

}

@OverridepublicString toString() {return super.toString();

}

}

/****/

packagecom.uc56.web.omg.authentication;importorg.apache.shiro.session.Session;importorg.apache.shiro.session.mgt.SessionContext;importorg.apache.shiro.session.mgt.SessionFactory;public class ShiroSessionFactory implementsSessionFactory {

@OverridepublicSession createSession(SessionContext initData) {

ShiroSession session= newShiroSession();returnsession;

}

}

/login/login.do=anon

/login/loginAuthc.do=anon

/login/authCheck.do=anon

/login/forbidden.do=anon

/login/validateUser.do=anon

/city/**=anon

/easyui-themes/**=anon

/images/**=anon

/jquery-easyui-1.5.1/**=anon

/scripts/**=anon

/users/**=anon

/**=LoginFailureCheck,authc,user

/>

此设计中最重要的一点就是:

1.cache中的session只存储不更新,也就是说每次访问不会刷新缓存中的session,cache中的session一定会在设定的时间中过期

2.cache中设置的session的时间一定要短于redis中存储的session,保证redis中session过期是,cache中的session一定过期

3.redis中的session更新会清楚cache中的session保证session一直性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值