Session 共享
1. 分布式 Session 问题
Section titled “1. 分布式 Session 问题”单机部署时,Session 存储在服务器内存中,没有问题。多实例部署后,负载均衡将请求分发到不同实例,Session 无法共享:
单机(正常):客户端 ──▶ 实例 A ──▶ 内存 Session ✅
多实例(Session 丢失):登录请求 ──▶ 实例 A(Session 存在 A 的内存)后续请求 ──▶ 实例 B(B 的内存没有该 Session)──▶ 未登录 ❌后续请求 ──▶ 实例 C(C 的内存没有该 Session)──▶ 未登录 ❌三种解决方案:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| IP 粘滞(Sticky Session) | 同一 IP 固定路由到同一实例 | 无需改造 | 实例宕机 Session 丢失,负载不均 |
| Session 复制 | 每个实例同步全量 Session | 无单点问题 | 网络开销大,内存占用高 |
| 集中式 Session 存储 | Session 存 Redis,所有实例共享 | 扩展性好,推荐 ✅ | 依赖 Redis 可用性 |
2. Spring Session + Redis
Section titled “2. Spring Session + Redis”Spring Session 是 Spring 提供的 Session 管理框架,无缝替换原生 HttpSession,将 Session 数据透明地存储到 Redis,业务代码无需任何改动。
请求 ──▶ Spring Session 过滤器 │ ├── 从 Cookie 取 sessionId ├── 用 sessionId 从 Redis 读取 Session 数据 └── 包装为 HttpSession 传入业务代码
业务代码:request.getSession() ──▶ 实际读写 Redis(透明)3. 快速集成
Section titled “3. 快速集成”3.1. 添加依赖
Section titled “3.1. 添加依赖”<!-- Spring Session Redis --><dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId></dependency>
<!-- Redis 客户端 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>3.2. 配置
Section titled “3.2. 配置”spring: data: redis: host: localhost port: 6379 password: 123456
session: store-type: redis # 使用 Redis 存储 Session timeout: 30m # Session 过期时间,默认 30 分钟 redis: namespace: spring:session # Redis 中 key 的命名空间前缀 flush-mode: on-save # 何时将 Session 写入 Redis:on-save(修改时)/ immediate(立即)3.3. 开启 Spring Session
Section titled “3.3. 开启 Spring Session”@SpringBootApplication@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) // 1800s = 30 分钟public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}4. 业务代码使用(零改造)
Section titled “4. 业务代码使用(零改造)”Spring Session 集成后,业务代码照常使用 HttpSession,无需任何修改:
@RestController@RequestMapping("/auth")public class AuthController {
// 登录:将用户信息存入 Session @PostMapping("/login") public String login(@RequestParam String username, @RequestParam String password, HttpSession session) { User user = authService.verify(username, password); if (user == null) return "用户名或密码错误";
session.setAttribute("currentUser", user); // 实际写入 Redis return "登录成功,sessionId: " + session.getId(); }
// 获取当前用户:从 Session 读取 @GetMapping("/me") public User getCurrentUser(HttpSession session) { return (User) session.getAttribute("currentUser"); // 实际从 Redis 读取 }
// 登出:清除 Session @PostMapping("/logout") public String logout(HttpSession session) { session.invalidate(); // 删除 Redis 中的 Session 数据 return "已退出登录"; }}5. Redis 中的 Session 存储结构
Section titled “5. Redis 中的 Session 存储结构”Spring Session 在 Redis 中以 Hash 结构存储 Session 数据:
spring:session:sessions:<sessionId> ← Hash,存储 Session 属性├── sessionAttr:currentUser → {"id":1,"name":"Alice",...}├── creationTime → 1710000000000├── lastAccessedTime → 1710000060000└── maxInactiveInterval → 1800
spring:session:sessions:expires:<sessionId> ← String,用于过期通知spring:session:expirations:<时间戳> ← Set,记录该时刻过期的 sessionId# 在 Redis 管理台中查看 SessionKEYS spring:session:sessions:*HGETALL spring:session:sessions:<sessionId>TTL spring:session:sessions:<sessionId>6. Cookie 配置
Section titled “6. Cookie 配置”Spring Session 默认通过 Cookie 传递 sessionId,可按需自定义:
@Configurationpublic class SessionConfig {
@Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer = new DefaultCookieSerializer(); serializer.setCookieName("SESSION"); // 自定义 Cookie 名(默认 SESSION) serializer.setDomainName("example.com"); // 跨子域共享 Session serializer.setCookiePath("/"); serializer.setHttpOnly(true); // 禁止 JS 访问 serializer.setUseSecureCookie(true); // 仅 HTTPS 传输 serializer.setCookieMaxAge(1800); // Cookie 有效期(秒) return serializer; }}7. 常见问题
Section titled “7. 常见问题”Q:Session 中存储的对象需要实现 Serializable 吗?
默认使用 JDK 序列化时需要。若配置了 JSON 序列化则不需要:
@Beanpublic RedisSerializer<Object> springSessionDefaultRedisSerializer() { // 使用 JSON 序列化替代 JDK 序列化 return new GenericJackson2JsonRedisSerializer();}Q:多个服务(微服务)如何共享 Session?
所有服务指向同一个 Redis,并配置相同的 namespace 和 cookieName,即可实现跨服务 Session 共享(单点登录基础方案)。
Q:Session 过期后如何通知业务?
// 实现 HttpSessionListener 或监听 Spring Session 事件@Componentpublic class SessionExpiredListener implements ApplicationListener<SessionExpiredEvent> {
@Override public void onApplicationEvent(SessionExpiredEvent event) { String sessionId = event.getSessionId(); log.info("Session 过期:{}", sessionId); // 可在此处清理业务数据,如在线用户列表 }}8. 架构对比总结
Section titled “8. 架构对比总结”传统 Session(单机):实例 A ──▶ 内存 Session ↑ 仅本实例可见
Spring Session + Redis(分布式):实例 A ──▶ Redis(集中存储)◀── 实例 B实例 C ──▶ ↑ 所有实例共享,水平扩展无限制