【推荐】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();
}
来源:oschina
链接:https://my.oschina.net/u/947581/blog/113350