shiro用memcache管理session頻繁讀取和更新session的問題
專案開發用到shiro來管理使用者的許可權,用memcache的超時機制來管理shiro的session.但是發現在執行專案的時候,訪問一個頁面控制檯會打出很多讀取和更新session的日誌內容。在通過測試之後發現一次訪問shiro自身會去讀取和更新多次session。這樣如果使用者多了memcache的壓力會比較大。所以就思考怎麼樣能夠減少對memcache的訪問。自己看了一下shiro的原始碼,只要請求進入了shiro的攔截器鏈,那麼shiro自身在初始化Subject的時候會多次的讀取session,那麼我們讓靜態資源的訪問不進入shiro的攔截器鏈,我們可以在專案中覆蓋AbstractShiroFilter類,這個是shiro攔截器鏈的入口程式,程式碼如果下
package org.apache.shiro.web.servlet; public abstract class AbstractShiroFilter extends OncePerRequestFilter{ //只貼出重要的程式碼 統一把靜態資源放在一個檔案裡,便於好管理 private static PatternMatcher staticResourcePathMatcher = new AntPathMatcher(); private static final String staticResourceAnt = "/static/**"; protected void doFilterInternal(ServletRequest servletRequest,ServletResponse servletResponse, ,final FilterChain chain){ Throwable t = null; try{ final ServletRequest request = prepareServletRequest(servletRequest, servletResponse,chain); final ServletResponse response = prepareServletResponse(servletRequest, servletResponse,chain); String requestURI = WebUtils.getPathWithinApplication(WebUtil,toHttp(request)); if(staticResourcePathMatcher.matches(staticResourceAnt,requestURI) ){ log.debug("過濾靜態資源"+requestURI); chain.doFilter(request,response); }else{ .......這裡就是shiro原生進入攔截器鏈的入口 } } } }
上面的是對入口的直接過濾,下面我們對進入shiro攔截器鏈的請求進行處理。
我是寫了一個類MySessionDao繼承與EnterpriseCacheSessionDAO類,這樣就可以把shiro的session用memcache來管理了。減少session的思路主要是在類MySessionDao中也儲存一份本地的session,當shiro在讀取session的時候,先通過session的id獲取本地的,如果沒有的話,那麼直接讀取memcaceh。然後拿本地的session的最後訪問時間(lastAccessTime)與當前的時間做一個時間差,如果差在自己設定的範圍之後那麼讀取memcaceh,如果不是的話就返回本地的session。這個時間間隔最好是設定的小一點,如果太大了有可能讀取不到最新的session.fdf
shiro在讀取session之後它會去更新最後的訪問時間,然後呼叫doUpdate方法更新session,基本上一次請求會更新一次session.為了減少session的更新我想到了以下的操作:session的更新分成兩種情況:1.是使用者呼叫了setAttribute方法;2.更新session的最後訪問時間。有以上的分析我覆蓋了shiro的SimpleSession淚,在它的setAttribute和removeAttribute方法做了一些處理,邏輯程式碼如下:
public class SimpleSession implements ValidatingSession, Serializable {
.... 原生程式碼....
public static final String SESSION_UPDATE_FALG = "SESSION_UPDATE_FALG";
public static final String SESSION_TIMESTAMP_FOR_UPDATE = "SESSION_TIMESTAMP_FOR_UPDATE";
public void setAttribute(Object key, Object value) {
//使用者設定屬性的時候,對當前session設定可以更新標識
if (value == null) {
removeAttribute(key);
} else {
getAttributesLazy().put(key, value);
}
if(!SESSION_UPDATE_FALG.equals(key)&&!SESSION_TIMESTAMP_FOR_UPDATE.equals(key)){
getAttributesLazy().put(SESSION_UPDATE_FALG, true);
}
}
public Object removeAttribute(Object key) {
Map<Object, Object> attributes = getAttributes();
if (attributes == null) {
return null;
} else {
if(!SESSION_UPDATE_FALG.equals(key)){
getAttributesLazy().put(SESSION_UPDATE_FALG, true);
}
return attributes.remove(key);
}
}
public void setLastAccessTime(Date lastAccessTime) {
if(getAttributes()!=null&&getAttributes().get(SESSION_TIMESTAMP_FOR_UPDATE)==null){
getAttributes().put(SESSION_TIMESTAMP_FOR_UPDATE, lastAccessTime);
System.out.println("SESSION_TIMESTAMP_FOR_UPDATE-------");
}
this.lastAccessTime = lastAccessTime;
}
.... 原生程式碼....
}
在shiro呼叫doUpdate這方法的時候可以去通過SESSION_UPDATE_FALG這標識獲取是否需要更新,
然後再通過這個SESSION_TIMESTAMP_FOR_UPDATE獲取更新時間與當前session的最後訪問時間做差。
比較自己設定的閾值,
這個可以設定的長一點比如10秒。
package com.unimas.modules.shiro;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.unimas.freamwork.memcached.CacheUtil;
import com.unimas.util.DateUtils;
public class MySessionDao extends EnterpriseCacheSessionDAO{
private static Logger log = LoggerFactory.getLogger(MySessionDao.class);
public static final String SHIRO_SESSION_FLAG = "shiro_session_flag_";
/**
* 系統本地也儲存一套使用者session
*/
static Map<String,Session> sessionCache = new HashMap<String, Session>();
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
log.debug("建立session id["+session.getId()+",ip["+session.getHost()+"" +
",startTime["+DateUtils.DATE_FORMAT_YYYY_MM_DDkgHHmhMMmmss.format( session.getStartTimestamp() )+" ]," +
",alstTime["+DateUtils.DATE_FORMAT_YYYY_MM_DDkgHHmhMMmmss.format( session.getLastAccessTime())+"]" +
","+session.toString());
String key = SHIRO_SESSION_FLAG+sessionId.toString();
long timeOut = session.getTimeout();
int expire = new Long(timeOut/1000).intValue();
CacheUtil.add(key, expire, session);
sessionCache.put(sessionId.toString(), session);
return sessionId;
}
protected Session doReadSession(Serializable sessionId) {
String key = SHIRO_SESSION_FLAG+sessionId.toString();
Session session = sessionCache.get(sessionId.toString());
if(session!=null){
Date lastAccessTime = session.getLastAccessTime();
long expireTimeMillis = System.currentTimeMillis() - lastAccessTime.getTime();
//處理在shiro建立Subjuect時多次讀取session的問題。
if(expireTimeMillis>600){
session = (Session)CacheUtil.get(key);
}
}else{
session = (Session)CacheUtil.get(key);
}
if(session!=null){
//設定本地的訪問時間LastAccessTime
SimpleSession simpleSession = (SimpleSession)session;
simpleSession.setLastAccessTime(new Date());
sessionCache.put(sessionId.toString(), session);
}
return session;
}
protected void doUpdate(Session session) {
SimpleSession simpleSession = (SimpleSession)session;
long timeOut = simpleSession.getTimeout();
int expire = new Long(timeOut/1000).intValue();
String key = SHIRO_SESSION_FLAG+session.getId().toString();
Boolean uf = (Boolean)session.getAttribute(SimpleSession.SESSION_UPDATE_FALG);
uf = uf==null?false:uf;
if(!uf){
long cacheSessionLastAccessTime = 0;
long currentSessionLastAccessTime = session.getLastAccessTime().getTime();
Date cacheSessionLastAccessDate = (Date)simpleSession.getAttribute(SimpleSession.SESSION_TIMESTAMP_FOR_UPDATE);
if(cacheSessionLastAccessDate==null){
cacheSessionLastAccessTime = currentSessionLastAccessTime;
}else{
cacheSessionLastAccessTime = cacheSessionLastAccessDate.getTime();
}
if(currentSessionLastAccessTime-cacheSessionLastAccessTime>10000){
session.setAttribute(SimpleSession.SESSION_UPDATE_FALG, false);
session.setAttribute(SimpleSession.SESSION_TIMESTAMP_FOR_UPDATE, simpleSession.getLastAccessTime());
CacheUtil.put(key, expire, session);
sessionCache.put(session.getId().toString(), session);
}
}else{
session.setAttribute(SimpleSession.SESSION_TIMESTAMP_FOR_UPDATE, simpleSession.getLastAccessTime());
session.setAttribute(SimpleSession.SESSION_UPDATE_FALG, false);
CacheUtil.put(key, expire, session);
sessionCache.put(session.getId().toString(), session);
}
}
protected void doDelete(Session session) {
//刪除登陸的佇列
String key = SHIRO_SESSION_FLAG+session.getId().toString();
CacheUtil.delete(key);
OnLineUser.deleteOnLineUser(session.getId().toString());
sessionCache.remove(key);
}
/**
* 提供獲取session的介面
*/
public Session readSession(Serializable sessionId){
return doReadSession(sessionId);
}
}