Session 的底层实现原理是什么(以 Tomcat 为例)?

深入 Tomcat 源码:Session 的底层实现原理
1. 引言:从超市储物柜说起
想象你去超市购物,在入口处存了一个背包。工作人员递给你一张小纸条(Session ID),你的背包则被锁进一个储物柜里。这个储物柜放在服务台后面,由工作人员(Manager)统一管理:他会根据小纸条找到你的柜子,也会定期清理长期无人认领的柜子。
在 Tomcat 的世界里,Session 就是那个储物柜,而 Manager 就是管理储物柜的工作人员。本文将从源码层面,深入剖析 Tomcat 是如何创建、存储、清理和传递 Session 的。读完你会明白:为什么 Session 能跨请求识别用户?为什么它有时会失效?以及如何优化 Session 配置。
2. 前置知识:Session 是什么?它解决什么问题?
HTTP 协议是无状态的——服务器不会记住两次请求之间的关系。为了让服务器能认出“你是谁”,我们需要一种机制来维持状态。Session 就是服务端为每个用户开辟的一块私有数据空间,用来存储登录状态、购物车等信息。每个 Session 有一个唯一的 Session ID,客户端每次请求时带上这个 ID,服务器就能找到对应的数据。
在 Tomcat 中,这一切都由 Manager 和 Session 两个核心组件完成。
3. Tomcat Session 核心架构
Tomcat 的 Session 管理围绕两个核心接口展开:
| 接口 | 职责 | 默认实现 |
|---|---|---|
Manager |
负责 Session 的创建、查找、销毁、持久化 | StandardManager |
Session |
代表一个用户会话,存储属性、过期时间、Session ID 等 | StandardSession |
它们之间的关系可以用下图表示:
- 每个
Context(即一个 Web 应用)拥有一个独立的Manager。 Manager内部维护一个ConcurrentHashMap,以 Session ID 为键,StandardSession为值。StandardSession内部使用ConcurrentHashMap存储用户自定义的属性(如用户名、购物车等),保证线程安全。
4. Session 的创建与 ID 生成
4.1 创建入口
当你在 Servlet 中调用 request.getSession() 时,Tomcat 会触发一系列操作:
// 我们的代码
HttpSession session = request.getSession();
Tomcat 的处理流程如下:
- 检查当前 Request 是否已有 Session:Tomcat 的
Request对象会复用,所以先检查内部是否已关联 Session。 - 尝试根据请求中的 Session ID 查找:如果请求携带了
JSESSIONID(通过 Cookie 或 URL),则尝试从Manager中查找对应的 Session。 - 若未找到,则创建新 Session:调用
Manager.createSession()。
4.2 Session ID 的生成
Tomcat 默认使用 java.security.SecureRandom 生成 Session ID,确保唯一性、随机性和不可预测性:
// ManagerBase.createSession() 核心逻辑
String id = generateSessionId(); // 生成 32 位十六进制字符串
// generateSessionId() 内部
byte[] randomBytes = new byte[16]; // 128 位
SecureRandom secureRandom = getSecureRandom();
secureRandom.nextBytes(randomBytes);
String sessionId = DigestUtils.sha256Hex(randomBytes + System.currentTimeMillis() + jvmId);
关键点:
- 使用 密码学安全的随机数生成器(CSPRNG),防止被暴力猜测。
- 长度 32 字符(128 位),满足高熵值要求。
- 生成后会检查是否与已有 ID 重复(概率极低,但做了防御)。
4.3 Session 对象的创建
// StandardManager.createSession()
Session session = createEmptySession(); // 创建 StandardSession 实例
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60); // 默认 30 分钟
session.setId(id);
创建完成后,Session 会被存入 Manager 的 ConcurrentHashMap 中,并与当前请求绑定。
5. Session ID 的传递:Cookie 与 URL 重写
Session ID 需要从服务器传递给客户端,并在后续请求中带回。Tomcat 默认通过 Cookie 传递。
5.1 Cookie 方式(默认)
当 Session 首次创建或 ID 发生变化时,Tomcat 会在响应头中添加 Set-Cookie:
Set-Cookie: JSESSIONID=D5A5C79F3C8E8653BC8B4F0860BFDBCD; Path=/; HttpOnly
浏览器收到后,会在后续请求的 Cookie 头中自动携带。
5.2 URL 重写(降级方案)
如果浏览器禁用了 Cookie,可以通过 URL 重写传递 Session ID。Tomcat 提供 response.encodeURL() 方法,将 jsessionid 附加到 URL 中:
http://example.com/index.jsp;jsessionid=D5A5C79F3C8E8653BC8B4F0860BFDBCD
注意:URL 重写存在安全风险(Session ID 暴露在地址栏、历史记录、Referer 头中),生产环境应尽量依赖 Cookie。
6. Session 的生命周期管理:过期与清理
6.1 后台线程机制
Tomcat 所有容器组件(Engine、Host、Context、Wrapper)都继承自 ContainerBase,它会在启动时开启一个后台线程(ContainerBackgroundProcessor),定期执行清理任务。
- Engine 的
backgroundProcessorDelay默认设为 10 秒。 - 父容器会递归调用子容器的
backgroundProcess()方法。
6.2 Session 过期检查
StandardContext.backgroundProcess() 会调用 Manager.backgroundProcess(),最终由 ManagerBase.processExpires() 执行过期清理:
public void processExpires() {
long timeNow = System.currentTimeMillis();
Session sessions[] = findSessions(); // 获取所有 Session
for (int i = 0; i < sessions.length; i++) {
if (sessions[i] != null && !sessions[i].isValid()) { // 检查是否过期
// 内部已执行 expire()
}
}
}
6.3 如何判断 Session 过期?
StandardSession.isValid() 的核心逻辑:
if (maxInactiveInterval > 0) {
int timeIdle = (int) (getIdleTimeInternal() / 1000L);
if (timeIdle >= maxInactiveInterval) {
expire(true); // 触发过期销毁
}
}
getIdleTimeInternal() 返回当前时间减去 lastAccessedTime(最近一次访问时间)。maxInactiveInterval 默认 1800 秒(30 分钟)。
6.4 过期销毁过程
expire() 方法做了以下工作:
- 加锁(
synchronized(this))防止并发。 - 双重校验,避免重复销毁。
- 触发
HttpSessionListener.sessionDestroyed()事件通知应用。 - 从
Manager的ConcurrentHashMap中移除该 Session。 - 清空 Session 内部的所有属性,帮助 GC 回收。
6.5 时间戳的更新
每次请求结束时,Tomcat 会更新 Session 的时间戳:
// Request.recycleSessionInfo()
if (session != null) {
session.endAccess(); // 更新时间戳
}
endAccess() 方法更新 lastAccessedTime 和 thisAccessedTime,保证活跃 Session 不会过期。
7. 存储扩展:从内存到持久化
7.1 Manager 的多种实现
Tomcat 提供了多种 Manager 实现,满足不同场景:
| 实现类 | 存储方式 | 特点 | 适用场景 |
|---|---|---|---|
StandardManager |
内存 | 默认实现,读写最快,重启丢失 | 单机、开发测试 |
PersistentManager |
内存 + 磁盘 | 支持钝化/活化,限制内存会话数 | 需要持久化,但不想引入外部存储 |
DeltaManager |
集群复制 | 变更同步到集群所有节点 | 集群会话(网络开销大) |
BackupManager |
集群备份 | 变更只同步给一个备份节点 | 集群会话(较 Delta 更轻量) |
7.2 钝化与活化
PersistentManager 支持将空闲 Session 写入磁盘(钝化),需要时再读回内存(活化)。这在内存受限时非常有用。
配置示例(context.xml):
<Manager className="org.apache.catalina.session.PersistentManager"
maxActiveSessions="100"
saveOnRestart="true">
<Store className="org.apache.catalina.session.FileStore" directory="sessions"/>
</Manager>
7.3 集群会话管理
在生产环境中,通常不依赖 Tomcat 自带的集群会话复制(DeltaManager),因为网络开销大、性能差。推荐使用 Redis 等外部集中存储,配合 Spring Session 实现分布式会话共享。
8. HttpSessionListener 的事件通知
8.1 Session 创建通知
Session 创建后,在 StandardSession.setId() 中会调用 tellNew(),触发 HttpSessionListener.sessionCreated() 事件:
public void tellNew() {
Context context = manager.getContext();
Object listeners[] = context.getApplicationLifecycleListeners();
HttpSessionEvent event = new HttpSessionEvent(getSession());
for (Object listener : listeners) {
if (listener instanceof HttpSessionListener) {
((HttpSessionListener) listener).sessionCreated(event);
}
}
}
8.2 Session 销毁通知
在 expire() 方法中,同样会遍历 HttpSessionListener,调用 sessionDestroyed(),通知应用 Session 已失效。
9. 常见误区与最佳实践
| 误区 | 正解 |
|---|---|
StandardManager 支持集群共享 |
❌ 错误。StandardManager 只存内存,无法跨节点共享。集群需用 DeltaManager 或外部存储。 |
| Session 超时时间越长越好 | ❌ 太长会增加内存占用和安全风险。建议 30 分钟,敏感应用可更短。 |
| Session 钝化就是分布式存储 | ❌ 钝化只是本地磁盘备份,不是跨节点共享。分布式需用 Redis 等。 |
| 用 URL 重写替代 Cookie | ❌ URL 重写不安全且体验差,仅作为降级方案。 |
最佳实践建议
- ✅ 生产环境优先使用 Redis 集中存储 Session,避免 Tomcat 原生集群复制带来的性能问题。
- ✅ 合理设置
maxInactiveInterval,既不过长也不过短。 - ✅ 登录成功后重新生成 Session ID(
session.changeSessionId()),防止会话固定攻击。 - ✅ 为
JSESSIONIDCookie 配置安全属性:HttpOnly、Secure、SameSite。
10. 总结
| 组件 | 职责 | 关键点 |
|---|---|---|
StandardSession |
存储用户数据 | 内部用 ConcurrentHashMap 存属性,记录创建时间、最后访问时间、最大空闲间隔 |
Manager |
管理 Session 生命周期 | 默认 StandardManager 用 ConcurrentHashMap 存 Session,后台线程定期清理过期 |
ContainerBackgroundProcessor |
后台线程调度 | 递归调用子容器的 backgroundProcess(),Engine 默认 10 秒一次 |
HttpSessionListener |
事件通知 | 创建和销毁时触发,供应用扩展 |
Tomcat 的 Session 管理是一套精妙的设计:用 Manager 统一管理、用后台线程清理过期、用 ConcurrentHashMap 保证线程安全。理解其内部机制,不仅能帮你写出更可靠的代码,还能在遇到分布式部署时,知道如何用 Redis 等方案替换默认实现,实现真正的高可用会话管理。
更多推荐

所有评论(0)