系列导读:本篇将深入讲解 Tomcat 会话管理的各种方案,重点介绍 Redis Session 共享的实现方式,解决集群环境下的会话一致性问题。
文章目录
-
- 前言:会话管理的挑战
- [一、Session 工作原理](#一、Session 工作原理)
-
- [1.1 Session 创建流程](#1.1 Session 创建流程)
- [1.2 Session 存储位置](#1.2 Session 存储位置)
- [1.3 Session 生命周期](#1.3 Session 生命周期)
- 二、会话管理方案对比
-
- [2.1 方案总览](#2.1 方案总览)
- [2.2 方案选择指南](#2.2 方案选择指南)
- [三、IP Hash 会话保持](#三、IP Hash 会话保持)
-
- [3.1 Nginx 配置](#3.1 Nginx 配置)
- [3.2 优缺点分析](#3.2 优缺点分析)
- [3.3 适用场景](#3.3 适用场景)
- [四、Tomcat Session 复制](#四、Tomcat Session 复制)
-
- [4.1 配置 server.xml](#4.1 配置 server.xml)
- [4.2 配置 web.xml](#4.2 配置 web.xml)
- [4.3 多实例配置](#4.3 多实例配置)
- [4.4 优缺点分析](#4.4 优缺点分析)
- [五、Redis Session 共享(推荐)](#五、Redis Session 共享(推荐))
-
- [5.1 架构设计](#5.1 架构设计)
- [5.2 Spring Boot + Redis Session](#5.2 Spring Boot + Redis Session)
- [5.3 Redis 配置优化](#5.3 Redis 配置优化)
- [5.4 Redis 集群配置](#5.4 Redis 集群配置)
- [5.5 Nginx 配置](#5.5 Nginx 配置)
- [5.6 优缺点分析](#5.6 优缺点分析)
- [六、JWT 无状态方案](#六、JWT 无状态方案)
-
- [6.1 JWT 原理](#6.1 JWT 原理)
- [6.2 JWT 结构](#6.2 JWT 结构)
- [6.3 Spring Boot JWT 实现](#6.3 Spring Boot JWT 实现)
- [6.4 优缺点分析](#6.4 优缺点分析)
- 总结
前言:会话管理的挑战
在单机 Tomcat 环境中,Session 存储在服务器内存中,用户请求始终由同一服务器处理,不存在会话问题。但在集群环境下:
┌─────────────────────────────────────────────────────────────┐
│ 集群会话问题示例 │
├─────────────────────────────────────────────────────────────┤
│ 1. 用户登录 → Nginx → Tomcat1(创建 Session,存储用户信息)│
│ 2. 用户请求 → Nginx → Tomcat2(找不到 Session,未登录状态)│
│ 3. 结果:用户被迫重新登录,体验极差 │
└─────────────────────────────────────────────────────────────┘
核心问题:Session 存储在各自 Tomcat 内存中,无法跨服务器共享。
一、Session 工作原理
1.1 Session 创建流程
客户端首次请求
↓
服务器创建 Session
↓
生成唯一 Session ID(如:JSESSIONID=A1B2C3D4E5F6)
↓
通过 Cookie 返回给客户端
↓
客户端后续请求携带 Cookie
↓
服务器根据 Session ID 查找 Session
1.2 Session 存储位置
| 存储位置 | 说明 | 适用场景 |
|---|---|---|
| 内存 | 默认方式,存储在 JVM 堆内存 | 单机应用 |
| 文件 | 持久化到磁盘 | 需要持久化的单机应用 |
| 数据库 | 存储在关系型数据库 | 需要持久查询 |
| Redis | 存储在 Redis 内存数据库 | 集群共享(推荐) |
| Memcached | 存储在 Memcached | 集群共享 |
1.3 Session 生命周期
java
// Session 默认配置
session.setMaxInactiveInterval(1800); // 30 分钟超时
// Session 事件
HttpSessionListener.sessionCreated() // Session 创建
HttpSessionListener.sessionDestroyed() // Session 销毁
HttpSessionAttributeListener // 属性变化
二、会话管理方案对比
2.1 方案总览
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| IP Hash | 同一 IP 固定服务器 | 配置简单 | 负载不均衡 | 小规模集群 |
| Session 复制 | 广播同步 Session | 实时同步 | 性能开销大 | 2-3 节点集群 |
| Redis 共享 | Session 存储在 Redis | 性能好、可扩展 | 需要 Redis | 大规模集群 |
| JWT 无状态 | Token 自包含信息 | 无服务器状态 | 无法主动失效 | RESTful API |
| Sticky Session | Cookie 绑定服务器 | 简单 | 服务器宕机丢失 | 简单应用 |
2.2 方案选择指南
集群规模 ≤ 3 节点?
├── 是 → Session 复制 或 IP Hash
└── 否 → Redis Session 共享
是否需要主动让用户下线?
├── 是 → Redis Session 共享
└── 否 → JWT 无状态
是否是 RESTful API?
├── 是 → JWT 无状态
└── 否 → Redis Session 共享
三、IP Hash 会话保持
3.1 Nginx 配置
nginx
upstream tomcat_cluster {
ip_hash; # 同一 IP 始终访问同一服务器
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://tomcat_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
3.2 优缺点分析
优点:
- ✅ 配置极其简单
- ✅ 无需修改应用代码
- ✅ 无额外组件依赖
缺点:
- ❌ 负载可能不均衡(某 IP 流量大)
- ❌ 服务器宕机会丢失该 IP 所有 Session
- ❌ 不适合移动端(IP 经常变化)
3.3 适用场景
✅ 内部系统(用户 IP 相对固定)
✅ 小规模集群(2-3 节点)
✅ 快速验证方案
✅ 不要求高可用
四、Tomcat Session 复制
4.1 配置 server.xml
xml
<!-- Tomcat conf/server.xml -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">
<!-- 集群配置 -->
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="6">
<!-- Session 管理器 -->
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<!-- 通信通道 -->
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<!-- 组播成员发现 -->
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<!-- 接收器 -->
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="5000"
selectorTimeout="100"
maxThreads="6"/>
<!-- 发送器 -->
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<!-- 拦截器 -->
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor"/>
</Channel>
<!-- 复制阀 -->
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=".*\.gif|.*\.js|.*\.jpeg|.*\.jpg|.*\.png|.*\.htm|.*\.html|.*\.css|.*\.txt"/>
<!-- 集群监听器 -->
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"/>
</Engine>
4.2 配置 web.xml
xml
<!-- WEB-INF/web.xml -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 启用分布式 -->
<distributable/>
</web-app>
4.3 多实例配置
每个 Tomcat 实例需要不同的配置:
xml
<!-- 实例1 -->
<Engine ... jvmRoute="tomcat1">
<Receiver ... port="5001"/>
<!-- 实例2 -->
<Engine ... jvmRoute="tomcat2">
<Receiver ... port="5002"/>
<!-- 实例3 -->
<Engine ... jvmRoute="tomcat3">
<Receiver ... port="5003"/>
4.4 优缺点分析
优点:
- ✅ 实时同步,无延迟
- ✅ 配置后无需修改应用代码
- ✅ 不依赖外部组件
缺点:
- ❌ 组播通信,性能开销大
- ❌ 节点越多,同步越慢
- ❌ 序列化开销
- ❌ 不适合大规模集群(建议 ≤ 4 节点)
五、Redis Session 共享(推荐)
5.1 架构设计
┌─────────────────────────────────────────────────────────────┐
│ 用户请求 │
└─────────────────────────┬───────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Nginx 负载均衡 │
└─────────────────────────┬───────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Tomcat 集群 │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Tomcat 1 │ │ Tomcat 2 │ │ Tomcat 3 │ │
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ └──────────────┼──────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Redis 集群 │
│ (Session 集中存储) │
└─────────────────────────────────────────────────────────────┘
5.2 Spring Boot + Redis Session
添加依赖:
xml
<!-- pom.xml -->
<dependencies>
<!-- Spring Session Redis -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- Spring Boot Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
配置 application.yml:
yaml
# application.yml
spring:
# Redis 配置
redis:
host: 192.168.1.100
port: 6379
password: yourpassword
database: 0
timeout: 10000
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
# Session 配置
session:
store-type: redis
redis:
namespace: spring:session
timeout: 1800 # 30 分钟超时
# 服务端口
server:
port: 8080
启用 Session:
java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@SpringBootApplication
@EnableRedisHttpSession // 启用 Redis Session
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Session 使用示例:
java
package com.example.demo.controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/session")
public class SessionController {
// 设置 Session
@PostMapping("/set")
public Map<String, Object> setSession(
@RequestParam String key,
@RequestParam String value,
HttpSession session) {
session.setAttribute(key, value);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("sessionId", session.getId());
result.put("key", key);
result.put("value", value);
return result;
}
// 获取 Session
@GetMapping("/get")
public Map<String, Object> getSession(
@RequestParam String key,
HttpSession session) {
Object value = session.getAttribute(key);
Map<String, Object> result = new HashMap<>();
result.put("sessionId", session.getId());
result.put("key", key);
result.put("value", value);
return result;
}
// 获取所有 Session 信息
@GetMapping("/info")
public Map<String, Object> getSessionInfo(HttpSession session) {
Map<String, Object> result = new HashMap<>();
result.put("sessionId", session.getId());
result.put("creationTime", session.getCreationTime());
result.put("maxInactiveInterval", session.getMaxInactiveInterval());
return result;
}
// 清除 Session
@PostMapping("/invalidate")
public Map<String, Object> invalidateSession(HttpSession session) {
String sessionId = session.getId();
session.invalidate();
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "Session invalidated");
result.put("sessionId", sessionId);
return result;
}
}
5.3 Redis 配置优化
java
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@Configuration
@EnableRedisHttpSession(
maxInactiveIntervalInSeconds = 1800, // Session 超时时间
redisNamespace = "app:session" // Redis key 前缀
)
public class SessionConfig {
// 使用 JSON 序列化
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
5.4 Redis 集群配置
yaml
# application.yml - Redis 集群
spring:
redis:
cluster:
nodes:
- 192.168.1.101:6379
- 192.168.1.102:6379
- 192.168.1.103:6379
- 192.168.1.104:6379
- 192.168.1.105:6379
- 192.168.1.106:6379
max-redirects: 3
password: yourpassword
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 2
5.5 Nginx 配置
nginx
upstream tomcat_cluster {
least_conn; # 使用最少连接算法
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
keepalive 32;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://tomcat_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
5.6 优缺点分析
优点:
- ✅ 性能优秀(Redis 内存存储)
- ✅ 可水平扩展
- ✅ 支持集群、哨兵、单机模式
- ✅ Session 持久化
- ✅ 支持主动失效
缺点:
- ❌ 需要 Redis 组件
- ❌ 序列化开销
- ❌ Redis 故障影响所有 Session
六、JWT 无状态方案
6.1 JWT 原理
用户登录
↓
服务器验证成功
↓
生成 JWT Token(包含用户信息)
↓
返回 Token 给客户端
↓
客户端存储 Token(LocalStorage/Cookie)
↓
后续请求携带 Token
↓
服务器验证 Token 签名
↓
解析用户信息
6.2 JWT 结构
Header.Payload.Signature
Header:
{
"alg": "HS256",
"typ": "JWT"
}
Payload:
{
"sub": "user123",
"name": "张三",
"role": "admin",
"exp": 1712345678
}
Signature:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
6.3 Spring Boot JWT 实现
添加依赖:
xml
<!-- pom.xml -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
JWT 工具类:
java
package com.example.demo.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUtil {
@Value("${jwt.secret:your-256-bit-secret-key-here-must-be-at-least-32-characters}")
private String secret;
@Value("${jwt.expiration:86400000}") // 默认 24 小时
private Long expiration;
// 生成密钥
private SecretKey getSecretKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
// 生成 Token
public String generateToken(String username, Map<String, Object> claims) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(getSecretKey(), SignatureAlgorithm.HS256)
.compact();
}
// 解析 Token
public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSecretKey())
.build()
.parseClaimsJws(token)
.getBody();
}
// 验证 Token
public boolean validateToken(String token) {
try {
parseToken(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
// 获取用户名
public String getUsername(String token) {
return parseToken(token).getSubject();
}
// 是否过期
public boolean isExpired(String token) {
return parseToken(token).getExpiration().before(new Date());
}
}
登录接口:
java
package com.example.demo.controller;
import com.example.demo.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private JwtUtil jwtUtil;
// 登录
@PostMapping("/login")
public Map<String, Object> login(@RequestParam String username,
@RequestParam String password) {
Map<String, Object> result = new HashMap<>();
// 验证用户名密码(示例)
if ("admin".equals(username) && "123456".equals(password)) {
Map<String, Object> claims = new HashMap<>();
claims.put("role", "admin");
claims.put("userId", 1);
String token = jwtUtil.generateToken(username, claims);
result.put("success", true);
result.put("token", token);
result.put("message", "登录成功");
} else {
result.put("success", false);
result.put("message", "用户名或密码错误");
}
return result;
}
// 验证 Token
@GetMapping("/verify")
public Map<String, Object> verify(@RequestHeader("Authorization") String authHeader) {
Map<String, Object> result = new HashMap<>();
String token = authHeader.replace("Bearer ", "");
if (jwtUtil.validateToken(token)) {
result.put("valid", true);
result.put("username", jwtUtil.getUsername(token));
} else {
result.put("valid", false);
}
return result;
}
}
6.4 优缺点分析
优点:
- ✅ 无状态,服务器不存储 Session
- ✅ 天然支持分布式
- ✅ 跨域友好
- ✅ 适合微服务架构
缺点:
- ❌ 无法主动让用户下线
- ❌ Token 泄露风险
- ❌ Token 较大,每次请求都携带
- ❌ 续期处理复杂
总结
本文深入讲解了 Tomcat 会话管理的各种方案:
✅ Session 原理 :创建流程、存储位置、生命周期
✅ 方案对比 :IP Hash、Session 复制、Redis 共享、JWT
✅ IP Hash :配置简单,负载不均
✅ Session 复制 :实时同步,性能开销大
✅ Redis Session :推荐方案,性能好可扩展
✅ JWT 无状态:适合 RESTful API
方案选择建议:
小规模集群(≤3 节点)→ IP Hash 或 Session 复制
大规模集群 → Redis Session 共享
RESTful API → JWT 无状态
需要主动下线用户 → Redis Session 共享
下一篇预告 :(Nginx + Tomcat 整合实战(五):性能优化与缓存策略),将深入讲解静态资源优化、缓存配置、JVM 调优等核心技能。
作者 :刘~浪地球
系列 :Nginx + Tomcat 整合实战(四)
更新时间:2026-03-31