網(wǎng)站建設(shè)需要什么樣的內(nèi)容輸入關(guān)鍵詞自動(dòng)生成標(biāo)題
tomcat session 設(shè)計(jì)分析
tomcat session 組件圖如下所示,其中?Context
?對(duì)應(yīng)一個(gè) webapp 應(yīng)用,每個(gè) webapp 有多個(gè)?HttpSessionListener
, 并且每個(gè)應(yīng)用的 session 是獨(dú)立管理的,而 session 的創(chuàng)建、銷毀由?Manager
?組件完成,它內(nèi)部維護(hù)了 N 個(gè)?Session
?實(shí)例對(duì)象。在前面的文章中,我們分析了?Context
?組件,它的默認(rèn)實(shí)現(xiàn)是?StandardContext
,它與?Manager
?是一對(duì)一的關(guān)系,Manager
?創(chuàng)建、銷毀會(huì)話時(shí),需要借助?StandardContext
?獲取?HttpSessionListener
?列表并進(jìn)行事件通知,而?StandardContext
?的后臺(tái)線程會(huì)對(duì)?Manager
?進(jìn)行過期 Session 的清理工作
org.apache.catalina.Manager
?接口的主要方法如下所示,它提供了?Context
、org.apache.catalina.SessionIdGenerator
?的 getter/setter 接口,以及創(chuàng)建、添加、移除、查找、遍歷?Session
?的 API 接口,此外還提供了?Session
?持久化的接口(load/unload) 用于加載/卸載會(huì)話信息,當(dāng)然持久化要看不同的實(shí)現(xiàn)類
public interface Manager {public Context getContext();public void setContext(Context context);public SessionIdGenerator getSessionIdGenerator();public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator);public void add(Session session);public void addPropertyChangeListener(PropertyChangeListener listener);public void changeSessionId(Session session);public void changeSessionId(Session session, String newId);public Session createEmptySession();public Session createSession(String sessionId);public Session findSession(String id) throws IOException;public Session[] findSessions();public void remove(Session session);public void remove(Session session, boolean update);public void removePropertyChangeListener(PropertyChangeListener listener);public void unload() throws IOException;public void backgroundProcess();public boolean willAttributeDistribute(String name, Object value);
tomcat8.5 提供了 4 種實(shí)現(xiàn),默認(rèn)使用?StandardManager
,tomcat 還提供了集群會(huì)話的解決方案,但是在實(shí)際項(xiàng)目中很少運(yùn)用,關(guān)于?Manager
?的詳細(xì)配置信息請(qǐng)參考?tomcat 官方文檔
- StandardManager:Manager 默認(rèn)實(shí)現(xiàn),在內(nèi)存中管理 session,宕機(jī)將導(dǎo)致 session 丟失;但是當(dāng)調(diào)用 Lifecycle 的 start/stop 接口時(shí),將采用 jdk 序列化保存 Session 信息,因此當(dāng) tomcat 發(fā)現(xiàn)某個(gè)應(yīng)用的文件有變更進(jìn)行 reload 操作時(shí),這種情況下不會(huì)丟失 Session 信息
- DeltaManager:增量 Session 管理器,用于Tomcat集群的會(huì)話管理器,某個(gè)節(jié)點(diǎn)變更 Session 信息都會(huì)同步到集群中的所有節(jié)點(diǎn),這樣可以保證 Session 信息的實(shí)時(shí)性,但是這樣會(huì)帶來較大的網(wǎng)絡(luò)開銷
- BackupManager:用于 Tomcat 集群的會(huì)話管理器,與DeltaManager不同的是,某個(gè)節(jié)點(diǎn)變更 Session 信息的改變只會(huì)同步給集群中的另一個(gè) backup 節(jié)點(diǎn)
- PersistentManager:當(dāng)會(huì)話長(zhǎng)時(shí)間空閑時(shí),將會(huì)把 Session 信息寫入磁盤,從而限制內(nèi)存中的活動(dòng)會(huì)話數(shù)量;此外,它還支持容錯(cuò),會(huì)定期將內(nèi)存中的 Session 信息備份到磁盤
Session 相關(guān)的類圖如下所示,StandardSession
?同時(shí)實(shí)現(xiàn)了?javax.servlet.http.HttpSession
、org.apache.catalina.Session
?接口,并且對(duì)外提供的是?StandardSessionFacade
?外觀類,保證了?StandardSession
?的安全,避免開發(fā)人員調(diào)用其內(nèi)部方法進(jìn)行不當(dāng)操作。而?org.apache.catalina.connector.Request
?實(shí)現(xiàn)了?javax.servlet.http.HttpServletRequest
?接口,它持有?StandardSession
?的引用,對(duì)外也是暴露?RequestFacade
?外觀類。而?StandardManager
?內(nèi)部維護(hù)了其創(chuàng)建的?StandardSession
,是一對(duì)多的關(guān)系,并且持有?StandardContext
?的引用,而?StandardContext
?內(nèi)部注冊(cè)了 webapp 所有的?HttpSessionListener
?實(shí)例。
創(chuàng)建Session
我們以?HttpServletRequest#getSession()
?作為切入點(diǎn),對(duì) Session 的創(chuàng)建過程進(jìn)行分析
public class SessionExample extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException {HttpSession session = request.getSession();// other code......}
整個(gè)流程圖如下圖所示(查看原圖):
tomcat 創(chuàng)建 session 的流程如上圖所示,我們的應(yīng)用程序拿到的?HttpServletRequest
?是?org.apache.catalina.connector.RequestFacade
(除非某些 Filter 進(jìn)行了特殊處理),它是?org.apache.catalina.connector.Request
?的門面模式。首先,會(huì)判斷?Request
?對(duì)象中是否存在?Session
,如果存在并且未失效則直接返回,因?yàn)樵?tomcat 中?Request
?對(duì)象是被重復(fù)利用的,只會(huì)替換部分組件,所以會(huì)進(jìn)行這步判斷。此時(shí),如果不存在?Session
,則嘗試根據(jù)?requestedSessionId
?查找?Session
,而該?requestedSessionId
?會(huì)在 HTTP Connector 中進(jìn)行賦值(如果存在的話),如果存在?Session
?的話則直接返回,如果不存在的話,則創(chuàng)建新的?Session
,并且把?sessionId
?添加到?Cookie
?中,后續(xù)的請(qǐng)求便會(huì)攜帶該?Cookie
,這樣便可以根據(jù)?Cookie
?中的sessionId
?找到原來創(chuàng)建的?Session
?了
在上面的過程中,Session
?的查找、創(chuàng)建都是由?Manager
?完成的,下面我們分析下?StandardManager
?創(chuàng)建?Session
?的具體邏輯。首先,我們來看下?StandardManager
?的類圖,它也是個(gè)?Lifecycle
?組件,并且?ManagerBase
?實(shí)現(xiàn)了主要的邏輯。
整個(gè)創(chuàng)建?Session
?的過程比較簡(jiǎn)單,就是實(shí)例化?StandardSession
?對(duì)象并設(shè)置其基本屬性,以及生成唯一的?sessionId
,其次就是記錄創(chuàng)建時(shí)間,關(guān)鍵代碼如下所示:
public Session createSession(String sessionId) {// 限制 session 數(shù)量,默認(rèn)不做限制,maxActiveSessions = -1if ((maxActiveSessions >= 0) &&(getActiveSessions() >= maxActiveSessions)) {rejectedSessions++;throw new TooManyActiveSessionsException(sm.getString("managerBase.createSession.ise"), maxActiveSessions);}// 創(chuàng)建 StandardSession 實(shí)例,子類可以重寫該方法Session session = createEmptySession();// 設(shè)置屬性,包括創(chuàng)建時(shí)間,最大失效時(shí)間session.setNew(true);session.setValid(true);session.setCreationTime(System.currentTimeMillis());// 設(shè)置最大不活躍時(shí)間(單位s),如果超過這個(gè)時(shí)間,仍然沒有請(qǐng)求的話該Session將會(huì)失效session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);String id = sessionId;if (id == null) {id = generateSessionId();}session.setId(id);sessionCounter++; // 這個(gè)地方不是線程安全的,可能當(dāng)時(shí)開發(fā)人員認(rèn)為計(jì)數(shù)器不要求那么準(zhǔn)確// 將創(chuàng)建時(shí)間添加到LinkedList中,并且把最先添加的時(shí)間移除,主要還是方便清理過期sessionSessionTiming timing = new SessionTiming(session.getCreationTime(), 0);synchronized (sessionCreationTiming) {sessionCreationTiming.add(timing);sessionCreationTiming.poll();}return (session);
在 tomcat 中是可以限制 session 數(shù)量的,如果需要限制,請(qǐng)指定?Manager
?的?maxActiveSessions
?參數(shù),默認(rèn)不做限制,不建議進(jìn)行設(shè)置,但是如果存在惡意攻擊,每次請(qǐng)求不攜帶?Cookie
?就有可能會(huì)頻繁創(chuàng)建?Session
,導(dǎo)致?Session
?對(duì)象爆滿最終出現(xiàn) OOM。另外?sessionId
?采用隨機(jī)算法生成,并且每次生成都會(huì)判斷當(dāng)前是否已經(jīng)存在該 id,從而避免?sessionId
?重復(fù)。而?StandardManager
?是使用?ConcurrentHashMap
?存儲(chǔ) session 對(duì)象的,sessionId
?作為 key,org.apache.catalina.Session
?作為 value。此外,值得注意的是?StandardManager
?創(chuàng)建的是 tomcat 的?org.apache.catalina.session.StandardSession
,同時(shí)他也實(shí)現(xiàn)了 servlet 的?HttpSession
,但是為了安全起見,tomcat 并不會(huì)把這個(gè)?StandardSession
?直接交給應(yīng)用程序,因此需要調(diào)用?org.apache.catalina.Session#getSession()
?獲取?HttpSession
。
我們?cè)賮砜纯?StandardSession
?的內(nèi)部結(jié)構(gòu)
- attributes:使用 ConcurrentHashMap 解決多線程讀寫的并發(fā)問題
- creationTime:Session 的創(chuàng)建時(shí)間
- expiring:用于標(biāo)識(shí) Session 是否過期
- lastAccessedTime:上一次訪問的時(shí)間,用于計(jì)算 Session 的過期時(shí)間
- maxInactiveInterval:Session 的最大存活時(shí)間,如果超過這個(gè)時(shí)間沒有請(qǐng)求,Session 就會(huì)被清理、
- listeners:這是 tomcat 的 SessionListener,并不是 servlet 的 HttpSessionListener
- facade:HttpSession 的外觀模式,應(yīng)用程序拿到的是該對(duì)象