Jetty源码学习11-Session

别来无恙 提交于 2019-12-19 00:30:21

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

引言

本文主要的内容有:解析session原理;总结jetty中的session体系;屡清session、sessionManager、sessionIdManager、sessionHandler之间的联系。

Session模型

SessionManager有多种实现,Jetty提供了HashSessionManage和JDBCSessionManager的实现,本文仅分析HashSessionManager体系。

1、SessionHandler的作用上文已经介绍过了,简单地说就是给request设置SessionMananger,供其在应用中使用,最后恢复request中的SessionManager,主要用在跨app的转发。

2、SessionManager如其名,起着管理app域session的作用,因此它必须依赖:Session(meta data)、Timer(定时器)、SessionIdManager(保证整个Jetty中sessionId的唯一性)

3、Session:一个k-v结构储存用户信息的类。

4、Timer:定时器,主要负责Session的过期处理和定时持久化Session的功能。

5、SessionIdManager:session的key即返回给客户端保存的JESSIONID,服务端亦据此取得用户的Session。该类负责生成服务器范围内独一无二的ID。

Create Session

对于本文HashSessionManager而言,session就是存在于服务端记录用户信息的map。

1、前置环境

应用中调用getSession时,而Request没有session时创建。

HttpSession session = request.getSession(true);

2、具体实现

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集中化管理。

3、后置处理

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

1、前置环境

应用中调用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清理掉,虽然不彻底,总比占着内存好吧!

2、具体实现

这是Request的getSession()实现

public HttpSession getSession(boolean create)
    {
        if (_session != null)
        {
            if (_sessionManager != null && !_sessionManager.isValid(_session))
                _session = null;
            else
                return _session;
        }
Save Session

1、前置环境

Session保存在内存中,无所谓保存的概念,这里的保存意为持久化。jetty中触发保存的因素为定时任务。

2、具体实现

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

1、前置环境

触发的因素是定时任务扫描失效的Session,有两类失效Session:分别为超过了session自身的最大闲置时间的和未超过session自身的最大闲置时间,但超过了服务端允许闲置session呆着的最大时间。前者处理比较彻底,后者主要是持久化后再清理attribute,以节约内存(为什么不清理session我纠结了,如果session清理掉,虽然持久化了,但是服务器并不认识~要补补脑了)。

2、具体实现

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();
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!