引言 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(); }