在这里插入图片描述

1. 引言:从超市储物柜说起

想象你去超市购物,在入口处存了一个背包。工作人员递给你一张小纸条(Session ID),你的背包则被锁进一个储物柜里。这个储物柜放在服务台后面,由工作人员(Manager)统一管理:他会根据小纸条找到你的柜子,也会定期清理长期无人认领的柜子。

在 Tomcat 的世界里,Session 就是那个储物柜,而 Manager 就是管理储物柜的工作人员。本文将从源码层面,深入剖析 Tomcat 是如何创建、存储、清理和传递 Session 的。读完你会明白:为什么 Session 能跨请求识别用户?为什么它有时会失效?以及如何优化 Session 配置。


2. 前置知识:Session 是什么?它解决什么问题?

HTTP 协议是无状态的——服务器不会记住两次请求之间的关系。为了让服务器能认出“你是谁”,我们需要一种机制来维持状态。Session 就是服务端为每个用户开辟的一块私有数据空间,用来存储登录状态、购物车等信息。每个 Session 有一个唯一的 Session ID,客户端每次请求时带上这个 ID,服务器就能找到对应的数据。

在 Tomcat 中,这一切都由 ManagerSession 两个核心组件完成。


3. Tomcat Session 核心架构

Tomcat 的 Session 管理围绕两个核心接口展开:

接口 职责 默认实现
Manager 负责 Session 的创建、查找、销毁、持久化 StandardManager
Session 代表一个用户会话,存储属性、过期时间、Session ID 等 StandardSession

它们之间的关系可以用下图表示:

Context(一个Web应用)

Manager(会话管理器)

Session 1

Session 2

Session 3

ConcurrentHashMap
存储属性

  • 每个 Context(即一个 Web 应用)拥有一个独立的 Manager
  • Manager 内部维护一个 ConcurrentHashMap,以 Session ID 为键,StandardSession 为值。
  • StandardSession 内部使用 ConcurrentHashMap 存储用户自定义的属性(如用户名、购物车等),保证线程安全。

4. Session 的创建与 ID 生成

4.1 创建入口

当你在 Servlet 中调用 request.getSession() 时,Tomcat 会触发一系列操作:

// 我们的代码
HttpSession session = request.getSession();

Tomcat 的处理流程如下:

  1. 检查当前 Request 是否已有 Session:Tomcat 的 Request 对象会复用,所以先检查内部是否已关联 Session。
  2. 尝试根据请求中的 Session ID 查找:如果请求携带了 JSESSIONID(通过 Cookie 或 URL),则尝试从 Manager 中查找对应的 Session。
  3. 若未找到,则创建新 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 会被存入 ManagerConcurrentHashMap 中,并与当前请求绑定。


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),定期执行清理任务。

  • EnginebackgroundProcessorDelay 默认设为 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() 方法做了以下工作:

  1. 加锁(synchronized(this))防止并发。
  2. 双重校验,避免重复销毁。
  3. 触发 HttpSessionListener.sessionDestroyed() 事件通知应用。
  4. ManagerConcurrentHashMap 中移除该 Session。
  5. 清空 Session 内部的所有属性,帮助 GC 回收。

6.5 时间戳的更新

每次请求结束时,Tomcat 会更新 Session 的时间戳:

// Request.recycleSessionInfo()
if (session != null) {
    session.endAccess();  // 更新时间戳
}

endAccess() 方法更新 lastAccessedTimethisAccessedTime,保证活跃 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 IDsession.changeSessionId()),防止会话固定攻击。
  • JSESSIONID Cookie 配置安全属性HttpOnlySecureSameSite

10. 总结

组件 职责 关键点
StandardSession 存储用户数据 内部用 ConcurrentHashMap 存属性,记录创建时间、最后访问时间、最大空闲间隔
Manager 管理 Session 生命周期 默认 StandardManagerConcurrentHashMap 存 Session,后台线程定期清理过期
ContainerBackgroundProcessor 后台线程调度 递归调用子容器的 backgroundProcess(),Engine 默认 10 秒一次
HttpSessionListener 事件通知 创建和销毁时触发,供应用扩展

Tomcat 的 Session 管理是一套精妙的设计:用 Manager 统一管理、用后台线程清理过期、用 ConcurrentHashMap 保证线程安全。理解其内部机制,不仅能帮你写出更可靠的代码,还能在遇到分布式部署时,知道如何用 Redis 等方案替换默认实现,实现真正的高可用会话管理。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐