1. 程式人生 > >shiro用memcache管理session頻繁讀取和更新session的問題

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);
	}
}