为什么需要 Session 共享?
在传统的单体应用中,用户的所有请求通常由同一个 Tomcat(或其他应用服务器)实例处理,Session 可以直接存储在该实例的内存中,但在分布式架构中,一个用户的请求可能会被负载均衡器分发到不同的服务器节点上。
- 问题:如果用户第一次请求到达 A 服务器,Session 信息被存储在 A 服务器的内存中,当用户的第二次请求被分发到 B 服务器时,B 服务器无法找到 A 服务器存储的 Session,导致用户需要重新登录,体验非常差。
- 解决方案:将 Session 数据从各个应用节点的本地内存中剥离出来,存放到一个所有节点都可以访问的共享存储中,当任何节点需要 Session 数据时,都从这个共享存储中读取,Redis 凭借其高性能、丰富的数据结构和持久化能力,成为了实现 Session 共享最主流的选择。
实现步骤详解
下面我们以最主流的 Spring Boot + Spring Session + Redis 的组合为例,讲解如何实现。
环境准备
- Java 项目:一个标准的 Spring Boot Web 项目。
- Redis 服务器:确保你已经安装并启动了 Redis 服务。
第一步:添加依赖
在你的 pom.xml 文件中,添加以下关键依赖:
<dependencies>
<!-- Spring Boot Starter Web (包含 Tomcat 等) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Session Data Redis (核心依赖) -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- Spring Boot Starter Data Redis (用于连接 Redis) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lombok (可选,简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
第二步:配置 Redis 和 Spring Session
在 application.properties 或 application.yml 文件中,配置 Redis 连接信息以及 Spring Session 的相关属性。
application.yml 示例:
# Redis 配置
spring:
redis:
host: 127.0.0.1
port: 6379
# 如果有密码,取消注释并填写
# password: yourpassword
# 如果使用本地 Docker 或其他环境,可能需要指定数据库索引
database: 0
# Spring Session 配置
spring:
session:
# 1. 指定存储类型为 Redis
store-type: redis
# 2. 设置 Session 过期时间 (单位: 秒),30 分钟
timeout: 1800
# 3. (可选) 设置 Redis 中 Session 的 key 的前缀
redis:
namespace: spring:session
第三步:添加 @EnableRedisHttpSession 注解
这是最关键的一步,创建一个配置类(通常就是主启动类),并添加 @EnableRedisHttpSession 注解。
这个注解会做以下几件事:

- 为你的应用注册一个
SessionRepositoryFilter过滤器,这个过滤器会拦截所有的 HTTP 请求,并用 Spring Session 提供的实现替换掉 Tomcat 原生的HttpSession实现。 - 当你调用
request.getSession()时,不再是创建或获取 Tomcat 的 Session,而是从 Redis 中创建或获取一个 Spring Session。 - 当 Session 被创建、修改或销毁时,Spring Session 会自动同步这些操作到 Redis 中。
主启动类示例:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@SpringBootApplication
// 启用 Redis-based HTTP session 支持
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) // 也可以在这里设置过期时间
public class SessionSharingApplication {
public static void main(String[] args) {
SpringApplication.run(SessionSharingApplication.class, args);
}
}
第四步:在 Controller 中使用 Session
你可以像往常一样在 Controller 中使用 HttpSession,但背后的一切都已经交给了 Redis。

Controller 示例:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import java.util.UUID;
@RestController
public class SessionController {
@GetMapping("/set-session")
public String setSession(HttpSession session) {
// 从 Session 中获取一个属性,如果不存在则存入一个随机 ID
String sessionId = (String) session.getAttribute("userId");
if (sessionId == null) {
sessionId = UUID.randomUUID().toString();
session.setAttribute("userId", sessionId);
return "Session attribute 'userId' has been set to: " + sessionId;
}
return "Session attribute 'userId' already exists: " + sessionId;
}
@GetMapping("/get-session")
public String getSession(HttpSession session) {
String sessionId = (String) session.getAttribute("userId");
if (sessionId == null) {
return "No 'userId' found in session. Please visit /set-session first.";
}
return "Current 'userId' in session is: " + sessionId;
}
@GetMapping("/invalidate-session")
public String invalidateSession(HttpSession session) {
session.invalidate(); // 销毁 Session,Redis 中的对应数据也会被删除
return "Session has been invalidated.";
}
}
工作原理解析
-
用户第一次访问
/set-session:
- 请求到达应用服务器 A。
SessionRepositoryFilter拦截请求,发现没有 Session ID(例如在 Cookie 中)。- 它会创建一个新的 Spring Session,并生成一个唯一的 ID(
a1b2c3d4-e5f6...)。 - 这个新 Session 会被序列化(通常使用 JSON)并存储到 Redis 中,Key 通常是
spring:session:sessions:a1b2c3d4-e5f6...,Value 是 Session 的内容。 - 一个名为
SESSION的 Cookie 会被设置到用户的浏览器中,值为这个 Session ID。 session.setAttribute("userId", ...)的操作会被捕获,并更新 Redis 中对应的 Session 数据。
-
用户第二次访问
/get-session:- 请求可能被负载均衡器分发到应用服务器 B。
SessionRepositoryFilter拦截请求,从浏览器的SESSIONCookie 中读取到 Session IDa1b2c3d4-e5f6...。- 它使用这个 ID 去连接 Redis,查找 Key 为
spring:session:sessions:a1b2c3d4-e5f6...的数据。 - 从 Redis 中成功读取到 Session 数据,包括之前设置的
userId。 - 服务器 B 成功获取到了 Session 信息,用户的无感登录体验得以实现。
生产环境最佳实践
-
Session 序列化方式:
- Spring Session 默认使用 Java 序列化,这种方式效率不高且存在安全风险。
- 强烈推荐使用 JSON 序列化,你可以配置
Jackson2JsonRedisSerializer。
自定义序列化配置示例:
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.session.web.http.CookieSerializer; import org.springframework.session.web.http.DefaultCookieSerializer; @Configuration public class RedisSessionConfig { @Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { ObjectMapper objectMapper = new ObjectMapper(); // 确保反序列化时能正确识别类型 objectMapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class); } // (可选) 自定义 Cookie 序列化器,解决域名/路径问题 @Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer = new DefaultCookieSerializer(); // 设置 Cookie 的域名,用于多级域名共享 // serializer.setCookieName("SESSION"); // serializer.setUseHttpCookieSerializer(true); // serializer.setUseBase64Encoding(false); // serializer.setCookiePath("/"); // serializer.setDomainNamePattern("^.+?\\.(\\w+
