引言 java
本文主要的內容有:解析session原理;總結jetty中的session體系;屢清session、sessionManager、sessionIdManager、sessionHandler之間的聯繫。 node
Session模型 react
SessionManager有多種實現,Jetty提供了HashSessionManage和JDBCSessionManager的實現,本文僅分析HashSessionManager體系。 緩存
一、SessionHandler的做用上文已經介紹過了,簡單地說就是給request設置SessionMananger,供其在應用中使用,最後恢復request中的SessionManager,主要用在跨app的轉發。 服務器
二、SessionManager如其名,起着管理app域session的做用,所以它必須依賴:Session(meta data)、Timer(定時器)、SessionIdManager(保證整個Jetty中sessionId的惟一性) cookie
三、Session:一個k-v結構儲存用戶信息的類。 session
四、Timer:定時器,主要負責Session的過時處理和定時持久化Session的功能。 app
五、SessionIdManager:session的key即返回給客戶端保存的JESSIONID,服務端亦據此取得用戶的Session。該類負責生成服務器範圍內獨一無二的ID。 框架
Create Session eclipse
對於本文HashSessionManager而言,session就是存在於服務端記錄用戶信息的map。
一、前置環境
應用中調用getSession時,而Request沒有session時建立。
HttpSession session = request.getSession(true);
二、具體實現
protected AbstractSession(AbstractSessionManager abstractSessionManager, HttpServletRequest request) { _manager = abstractSessionManager; _newSession=true; _created=System.currentTimeMillis(); _clusterId=_manager._sessionIdManager.newSessionId(request,_created); _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,request); _accessed=_created; _lastAccessed=_created; _requests=1; _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1; if (LOG.isDebugEnabled()) LOG.debug("new session & id "+_nodeId+" "+_clusterId); }
這裏有個概念,nodeId=clusterId+'.'+worker,worker惟一標示一條機器,這樣作的目的是爲應付集羣的環境。可是若是第二次請求依賴的指定機器掛了,就失去其設計的意義了,因此線上機器對於分佈式環境下的session有兩套方案:一爲加密存於客戶端;二爲session集中化管理。
三、後置處理
Request將持有session引用,並將nodeId做爲cookie的JESSIONID的value返回給客戶端。
_session = _sessionManager.newHttpSession(this); HttpCookie cookie = _sessionManager.getSessionCookie(_session,getContextPath(),isSecure()); if (cookie != null) _connection.getResponse().addCookie(cookie);服務端的SessionManager和SessionIdManager中緩存建立好的session
_sessionIdManager.addSession(session); addSession(session);
可是緩存在SessionIdManager比較費解,並且緩存是<String,set<HttpSession>>,難道一個id能夠配多個Session麼?
public void addSession(HttpSession session) { String id = getClusterId(session.getId()); WeakReference<HttpSession> ref = new WeakReference<HttpSession>(session); synchronized (this) { Set<WeakReference<HttpSession>> sessions = _sessions.get(id); if (sessions==null) { sessions=new HashSet<WeakReference<HttpSession>>(); _sessions.put(id,sessions); } sessions.add(ref); } }Get Session
一、前置環境
應用中調用getSession時,發現request中已經有session,直接獲取該引用。天然是在SessionHandler的doScope時根據request的cookie從服務端取session塞給request(感受了解了框架的設計意圖以後有些問題不用調試也能找到答案)。
protected void checkRequestedSessionId(Request baseRequest, HttpServletRequest request) { String requested_session_id = request.getRequestedSessionId(); SessionManager sessionManager = getSessionManager(); if (requested_session_id != null && sessionManager != null) { HttpSession session = sessionManager.getHttpSession(requested_session_id); if (session != null && sessionManager.isValid(session)) baseRequest.setSession(session); return; }
若是系統重啓後沒有加載過持久化的session,即從文件系統中加載
protected synchronized HashedSession restoreSession(String idInCuster) { File file = new File(_storeDir,idInCuster); try { if (file.exists()) { FileInputStream in = new FileInputStream(file); HashedSession session = restoreSession(in, null); in.close(); addSession(session, false); session.didActivate(); file.delete(); return session; } }
若是加載的session發現是被閒置的session,那麼服務器會再次從文件系統中讀取session更新內存中的session(有些session好久不用會被服務器持久化並清理其中的attribute以節約內存)
public AbstractSession getSession(String idInCluster) { if ( _lazyLoad && !_sessionsLoaded) { try { restoreSessions(); } catch(Exception e) { __log.warn(e); } } Map<String,HashedSession> sessions=_sessions; if (sessions==null) return null; //標註 HashedSession session = sessions.get(idInCluster); if (session == null && _lazyLoad) session=restoreSession(idInCluster); if (session == null) return null; //若是session被idle過,將從文件系統讀取文件更新此session if (_idleSavePeriodMs!=0) session.deIdle(); return session; }
注意上面我標註的代碼,你可曾想過:既然每次都會從文件系統中單獨加載特定id的session,爲何在idle的時候不直接把session給remove掉呢?主要緣由是因爲,下次若是一樣的用戶過來訪問你豈不是給他新的一個JESSIONID,他明明之前的JESSIONID設置過永不失效的,這樣就有衝突了~所以最好的解決方案就是隻把session持有的attribute清理掉,雖然不完全,總比佔着內存好吧!
二、具體實現
這是Request的getSession()實現
public HttpSession getSession(boolean create) { if (_session != null) { if (_sessionManager != null && !_sessionManager.isValid(_session)) _session = null; else return _session; }Save Session
一、前置環境
Session保存在內存中,無所謂保存的概念,這裏的保存意爲持久化。jetty中觸發保存的因素爲定時任務。
二、具體實現
try { file = new File(_hashSessionManager._storeDir, super.getId()); if (file.exists()) file.delete(); file.createNewFile(); fos = new FileOutputStream(file); willPassivate(); save(fos); if (reactivate) didActivate(); else clearAttributes(); }Invalid Session
一、前置環境
觸發的因素是定時任務掃描失效的Session,有兩類失效Session:分別爲超過了session自身的最大閒置時間的和未超過session自身的最大閒置時間,但超過了服務端容許閒置session呆着的最大時間。前者處理比較完全,後者主要是持久化後再清理attribute,以節約內存(爲何不清理session我糾結了,若是session清理掉,雖然持久化了,可是服務器並不認識~要補補腦了)。
二、具體實現
for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();) { HashedSession session=i.next(); long idleTime=session.getMaxInactiveInterval()*1000L; if (idleTime>0&&session.getAccessed()+idleTime<now) { // Found a stale session, add it to the list //清緩存,清文件 session.timeout(); } else if (_idleSavePeriodMs>0&&session.getAccessed()+_idleSavePeriodMs<now) { //保存文件,清屬性 session.idle(); } }
Timer
幾個時間相關的參數(還有個session自身的超時時間)
//監控超時session的掃描週期 long _scavengePeriodMs=30000; //持久化session的執行週期 long _savePeriodMs=0; //don't do period saves by default //服務端容許閒置session存在的最大時間 long _idleSavePeriodMs = 0; // don't idle save sessions by default.
定時任務,獨立的線程,負責持久化任務和監控超時session的任務
public void doStart() throws Exception { super.doStart(); _timerStop=false; ServletContext context = ContextHandler.getCurrentContext(); if (context!=null) _timer=(Timer)context.getAttribute("org.eclipse.jetty.server.session.timer"); if (_timer==null) { _timerStop=true; _timer=new Timer("HashSessionScavenger-"+__id++, true); } setScavengePeriod(getScavengePeriod()); if (_storeDir!=null) { if (!_storeDir.exists()) _storeDir.mkdirs(); if (!_lazyLoad) restoreSessions(); } setSavePeriod(getSavePeriod()); }
定時任務的初始化的時候啓動線程
public Timer(String name, boolean isDaemon) { thread.setName(name); thread.setDaemon(isDaemon); thread.start(); }