Jetty源碼學習11-Session

引言 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();
    }
相關文章
相關標籤/搜索