Nginx + Tomcat 整合实战(四):会话管理与共享详解

系列导读:本篇将深入讲解 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

相关推荐
刘~浪地球2 小时前
Nginx + Tomcat 整合实战(一):基础环境搭建与整合入门
nginx·tomcat·firefox
曹牧2 小时前
Tomcat连接池异常排查
java·tomcat
RisunJan2 小时前
Linux命令-mv(移动或重命名文件和目录)
linux·运维·服务器
wh_xia_jun2 小时前
Windows/Linux 自动适配 + Pydantic Settings 配置
linux·运维·windows
为爱停留2 小时前
HTTPS 域名访问与 Nginx 全链路说明
网络协议·nginx·https
斌味代码2 小时前
Docker + 宝塔:容器化部署最佳实践(2026最新版)
运维·docker·容器
克莱因3582 小时前
Linux 进程监控
linux·运维·服务器
Agent产品评测局2 小时前
企业 HR 自动化落地,入转调离全流程自动化实现方法:基于企业级智能体的技术路径与方案盘点
运维·人工智能·ai·chatgpt·自动化
牛奶咖啡132 小时前
DevOps自动化运维实践_自动化运维工具Ansible
运维·自动化·ansible·devops·ansible的安装·ansible的架构与运行原理·ansible的主机和组配置