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 分钟前
RedTeam-Agent无需手动操作,AI 接管所有渗透工具,让安全测试真正自动化
运维·人工智能·自动化
小此方6 分钟前
Re:Linux系统篇(五)指令篇 ·四:shell外壳程序及其工作原理
linux·运维·服务器
其实防守也摸鱼27 分钟前
sqlmap下载和安装保姆级教程(附安装包)
linux·运维·服务器·测试工具·渗透测试·攻防·护网行动
jingyu飞鸟1 小时前
Linux系统发送邮件,解决信誉等级低问题 docker compose修改启动一键使用
linux·运维·docker
Lumos_7771 小时前
Linux -- exec 进程替换
linux·运维·chrome
李白客1 小时前
国产数据库选型指南:从技术路线到实战要点
运维·数据库·数据库架构·迁移学习
数智化精益手记局1 小时前
人员排班管理软件的自动化功能解析:解决传统手工人员进行排班管理耗时长的难题
运维·数据结构·人工智能·信息可视化·自动化·制造·精益工程
jy41932171 小时前
VPS 网络质量怎么测?一篇讲清楚多节点 ping、tcping 和回程路由
运维
RePeaT1 小时前
【Nginx】前端项目部署与反向代理实战指南
前端·nginx
wicb91wJ62 小时前
Nginx反向代理与负载均衡配置详解
运维·nginx·负载均衡