Session管理
Session可以用来管理用户的会话信息,最常见的就是拿Session来存放用户登录、身份、权限及状态等信息。对于使用Tomcat作为Web容器的大部分开发人员而言,本文介绍分析Tomcat是如何实现Session标记用户和管理Session信息
Session接口
Tomcat内部定义了Session和HttpSession这两个会话相关的接口,其类继承体系如下
Session:Tomcat中有关会话的基本接口规范,下面介绍它定义的主要方法
方法 | 描述 |
---|---|
getCreationTime()/setCreationTime() | 获取与设置Session的创建时间 |
getId()/setId() | 获取与设置Session的ID |
getThisAccessedTime() | 获取最近一次请求的开始时间 |
getLastAccessedTime() | 获取最近一次请求的完成时间 |
getManager()/setManager() | 获取与设置Session管理器 |
getMaxInactiveInterval() | 获取Session的最大访问间隔 |
setMaxInactiveInterval() | 设置Session的最大访问间隔 |
getSession() | 获取HttpSession |
setValid()/isValid() | 获取与设置Session的有效状态 |
access()/endAccess() | 开始与结束Session的访问 |
expire() | 设置Session过期 |
HttpSession:在HTTP客户端与HTTP服务端提供的一种会话的接口规范
方法 | 描述 |
---|---|
getCreationTime() | 获取Session的创建时间 |
getId() | 获取Session的ID |
getLastAccessedTime() | 获取最近一次请求的完成时间 |
getServletContext() | 获取当前Session所属的ServletContext |
getMaxInactiveInterval() | 获取Session的最大访问间隔 |
setMaxInactiveInterval() | 设置Session的最大访问间隔 |
getAttribute()/setAttribute() | 获取与设置Session作用域的属性 |
removeAttribute() | 清除Session作用域的属性 |
invalidate() | 使Session失效并解除任何与此Session绑定的 对象 |
ClusterSession:集群部署下的会话接口规范
方法 | 描述 |
---|---|
isPrimarySession() | 判断是否为集群的主Session |
setPrimarySession() | 设置集群主Session |
StandardSession:标准的HTTP Session实现,本文将以此实现为例展开
DeltaSession:Tomcat集群会话同步的策略,对会话中增量修改的属性进行同步。这种方式由于是增量的,所以会大大降低网络I/O的开销,但是是线上会比较复杂因为涉及到对会话属性操作过程的管理
ReplicationSessionListener:Tomcat集群会话同步的策略,每次都会把整个会话对象同步给集群中的其他节点,其他节点然后更新整个会话对象。这种实现比较简单但会造成大量无效信息的传输
Session管理器
Tomcat内部定义了Manager接口用于制定Session管理器的接口规范,目前已经有很多Session管理器的实现
Manager:Tomcat对于Session管理器定义的接口规范
方法 | 描述 |
---|---|
getContext()/setContext() | 获取与设置上下文 |
getSessionIdGenerator() | 获取会话id生成器 |
setSessionIdGenerator() | 设置会话id生成器 |
getSessionCounter() | 获取Session计数器 |
setSessionCounter() | 设置Session计数器 |
getMaxActive()/setMaxActive() | 获取与设置处于活动状态的最大会话数 |
getActiveSessions() | 获取处于活跃状态的会话数 |
getExpiredSessions() | 获取过期的会话数 |
setExpiredSessions() | 设置过期的会话数 |
getRejectedSessions() | 获取未创建的会话数 |
getSessionMaxAliveTime() | 获取会话存活的最长时间(单位为秒) |
setSessionMaxAliveTime() | 设置会话存活的最长时间(单位为秒) |
getSessionAverageAliveTime() | 获取会话平均存活时间 |
getSessionCreateRate() | 获取当前会话创建速率 |
getSessionExpireRate() | 获取当前会话过期速率 |
add() | 将此会话添加到处于活动状态的会话集合 |
addPropertyChangeListener() | 将属性更改监听器到此组件 |
changeSessionId() | 将当前会话的ID更改为新的随机生成的会话ID |
rotateSessionId() | 将当前会话的ID更改为新的随机生成的会话ID |
changeSessionId() | 将当前会话的ID更改为指定的会话ID |
createEmptySession() | 从回收的会话中获取会话或创建一个新的会话 |
createSession() | 根据默认值构造并返回一个新的会话对象 |
findSession() | 返回与此管理器关联的会话 |
findSessions() | 返回与此管理器关联的会话集合 |
load()/unload() | 从持久化机制中加载Session或向持久化机制写入Session |
remove() | 从此管理器的活动会话中删除此会话 |
removePropertyChangeListener() | 从此组件中删除属性更改监听器 |
backgroundProcess() | 容器接口中定义为具体容器在后台处理相关工作的实现,Session管理器基于此机制实现了过期Session的 |
willAttributeDistribute() | 管理器写入指定的会话属性 |
ManagerBase:封装了Manager接口通用实现的抽象类,未提供对load()/unload()等方法的实现,需要具体子类去实现。所有的Session管理器都集成自ManagerBase
ClusterManager:在Manager接口的基础上增加了集群部署下的一些接口,所有实现集群下Session管理器都要实现此接口
PersistentManagerBase:提供了对于Session持久化的基本实现
PersistentManager:继承自PersistentManagerBase,可以在Server.xml的元素下通过配置元素来使用。PersistentManager可以将内存中的Session信息备份到文件或数据库中。当备份一个Session对象时,该Session对象会被复制到存储器(文件或者数据库)中,而原对象仍然留在内存中。因此即便服务器宕机,仍然可以从存储器中获取活动的Session对象。如果活动的Session对象超过了上限值或者Session对象闲置了的时间过长,那么Session会被换出到存储器中以节省内存空间
StandardManager:不用配置元素,当Tomcat正常关闭,重启或Web应用重新加载时,它会将内存中的Session序列化到Tomcat目录的/work/Catalina/host_name/webapp_name/SESSIONS.ser
文件中。当Tomcat重启或者应用加载完成后,Tomcat会将文件中的Session重新还原到内存中。如果突然中止该服务器,则所有Session豆浆丢失,因为StandardManager没有机会实现存盘处理
ClusterManagerBase:提供了对于Session的集群管理实现
DeltaManager:继承自ClusterManagerBase。此Session管理器是Tomcat集群部署下的默认管理器,当集群中的某一节点生成或修改Session后,DeltaManager将会把这些修改增量复制到其他节点
BackupManager:没有继承ClusterManagerBase,而是直接实现了ClusterManager接口。是Tomcat在集群部署下的可选的Session管理器,集群中的所有Session都被全量复制到一个备份节点。集群中的所有节点都可以访问此备份节点,达到Session在集群下的备份效果
本文以StandardManager为例讲解Session的管理。StandardManager是StandardContext的子组件,用来管理当前Context的所有Session的创建和维护。由Tomcat生命周期管理可知,当StandardContext正式启动,也就是StandardContext的startInternal方法被调用时,StandardContext还会启动StandardManager
org.apache.catalina.core.StandardContext.startInternal()
@Override
protected synchronized void startInternal() throws LifecycleException {
// 省略与Session管理无关的代码
// Acquire clustered manager
Manager contextManager = null;
Manager manager = getManager();
if (manager == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.cluster.noManager",
Boolean.valueOf((getCluster() != null)),
Boolean.valueOf(distributable)));
}
if ((getCluster() != null) && distributable) {
try {
contextManager = getCluster().createManager(getName());
} catch (Exception ex) {
log.error(sm.getString("standardContext.cluster.managerError"), ex);
ok = false;
}
} else {
contextManager = new StandardManager();
}
}
// Configure default manager if none was specified
if (contextManager != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.manager",
contextManager.getClass().getName()));
}
setManager(contextManager);
}
if (manager!=null && (getCluster() != null) && distributable) {
//let the cluster know that there is a context that is distributable
//and that it has its own manager
getCluster().registerManager(manager);
}
}
// 省略与Session管理无关的代码
try {
// Start manager
Manager manager = getManager();
if (manager instanceof Lifecycle) {
((Lifecycle) manager).start();
}
} catch(Exception e) {
log.error(sm.getString("standardContext.managerFail"), e);
ok = false;
}
// 省略与Session管理无关的代码
}
从中可知StandardContext.startInternal()
中涉及Session管理的执行步骤如下:
- 创建StandardManager
- 如果Tomcat结合Apache做了分布式部署,会将当前StandardManager注册到集群中
- 启动StandardManager
StandardManger.start()
用于启动StandardManager
org.apache.catalina.util.LifecycleBase.start()
@Override
public final synchronized void start() throws LifecycleException {
// 省略状态校验的代码
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
从中可知启动StandardManager的步骤如下:
- 调用init方法初始化StandardManager
- 调用startInternal方法启动StandardManager
StandardManager的初始化
经上分析可知,启动StandardManager的第一部就是调用父类LifecycleBase的init方法,init方法在Tomcat生命周期管理中已介绍,现在只需要关心StandardManager的initInternal。StandardManager本身并没有实现initInternal方法,但是StandardManager的父类ManagerBase实现了此方法,将StandardManager注册为到 JMX
org.apache.catalina.session.ManagerBase.initInternal()
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
if (context == null) {
throw new LifecycleException(sm.getString("managerBase.contextNull"));
}
}
StandardManager的启动
调用StandardManager的startInternal方法用于启动StandardManager
org.apache.catalina.session.StandardManager.startInternal()
@Override
protected synchronized void startInternal() throws LifecycleException {
super.startInternal();
// Load unloaded sessions, if any
try {
load();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardManager.managerLoad")