Spring Session 实现分布式 Session 详解:基于 Redis 的配置与原理
在分布式系统中,传统的单机 Session(存储在 Web 容器内存中)会因 “多节点间 Session 不共享” 导致问题(如用户登录后切换节点需重新登录)。Spring Session 提供了优雅的解决方案:通过 HttpServletRequest 包装(Wrapper) 重写 Session 操作逻辑,将 Session 存储到分布式存储(如 Redis、MongoDB)中,实现多节点 Session 共享。从 “核心原理→依赖配置→实战步骤→底层逻辑” 四个维度,彻底讲透 Spring Session 的实现与使用。
分布式 Session 核心痛点与 Spring Session 解决方案
1. 传统单机 Session 的问题
在分布式部署(如多 Tomcat 节点负载均衡)场景下,传统 Session 存在以下问题:
- Session 不共享:用户请求被负载均衡分发到不同节点,各节点内存中的 Session 独立,导致用户登录状态丢失;
- Session 持久化差:Session 存储在节点内存中,节点重启后 Session 丢失;
- 扩展性差:无法支持大规模集群,节点数量增加会导致 Session 同步成本升高。
2. Spring Session 的核心思路
Spring Session 不依赖 Web 容器的 Session 实现,而是通过以下机制实现分布式 Session:
- 包装 HttpServletRequest:通过
HttpServletRequestWrapper重写getSession()方法,将 Session 操作委托给自定义的SessionRepository; - 分布式存储:
SessionRepository将 Session 数据存储到 Redis、MongoDB 等分布式存储中,而非节点内存; - 过滤器拦截请求:通过
DelegatingFilterProxy拦截所有请求,将原生HttpServletRequest替换为包装后的对象,确保所有 Session 操作都走分布式逻辑。
环境准备:依赖与 Redis 配置
Spring Session 支持多种存储介质,本文以 Redis(最常用)为例,需先配置依赖和 Redis 连接。
1. 依赖配置(Maven)
以下是更稳定的版本组合(适配 Spring 4.x/5.x):
1 | <!-- Spring Session 核心依赖(Redis 存储) --> |
依赖说明:
spring-session-data-redis:Spring Session 针对 Redis 的实现包;lettuce-core:Redis 客户端(替代老的jedis,支持异步、线程安全);spring-data-redis:Spring 封装的 Redis 操作工具,简化连接与序列化;jackson-databind:用于将 Session 中的 Java 对象序列化为 JSON 存储到 Redis。
2. Redis 服务准备
确保 Redis 服务已启动,且满足以下条件:
- Redis 版本 ≥ 2.8.0(支持过期键功能,用于 Session 过期清理);
- 分布式环境下,所有应用节点需连接同一 Redis 集群 / 实例(确保 Session 共享)。
Spring Session 配置(XML 方式)
核心是 “配置 Redis 连接→配置 Spring Session→配置过滤器拦截请求”。
1. 配置 Redis 连接工厂
Spring Session 通过 RedisConnectionFactory 与 Redis 交互,这里使用 LettuceConnectionFactory(默认推荐):
1 | <!-- 1. Redis 连接工厂(Lettuce) --> |
2. 配置 Spring Session 核心组件
通过 RedisHttpSessionConfiguration 自动配置 Spring Session 所需组件(如 SessionRepository、Session 序列化器):
1 | <!-- 2. Spring Session 核心配置(Redis 存储) --> |
关键配置说明:
maxInactiveIntervalInSeconds:Session 过期时间(如 1800 秒 = 30 分钟),过期后 Redis 会自动清理 Session 数据;defaultRedisSerializer:Session 数据序列化器,推荐使用GenericJackson2JsonRedisSerializer(JSON 格式,可读性高),替代默认的JdkSerializationRedisSerializer(二进制格式,可读性差);redisNamespace:Redis 键前缀(如myapp:session),避免不同应用的 Session 数据在 Redis 中冲突。
3. 配置过滤器:拦截请求并包装 HttpServletRequest
这是 Spring Session 生效的核心步骤!通过 DelegatingFilterProxy 拦截所有请求,将原生 HttpServletRequest 替换为 SessionRepositoryRequestWrapper(重写了 getSession() 方法)。
1 | <!-- 3. Spring Session 过滤器:必须在所有过滤器之前(尤其是 Spring MVC 的 DispatcherServlet 之前) --> |
为什么必须用 DelegatingFilterProxy?
“springSessionRepositoryFilter 没有默认构造器”,这是关键原因:
springSessionRepositoryFilter是 Spring 管理的 Bean(由RedisHttpSessionConfiguration自动创建),依赖SessionRepository等组件,无法通过new直接实例化;DelegatingFilterProxy是一个 “代理过滤器”,它会从 Spring 容器中查找名为springSessionRepositoryFilter的 Bean,并将过滤逻辑委托给该 Bean;- 过滤器名称必须为
springSessionRepositoryFilter:这是 Spring Session 的固定约定,RedisHttpSessionConfiguration会创建同名的过滤器 Bean。
实战使用:像操作普通 Session 一样使用分布式 Session
配置完成后,开发者无需修改任何业务代码,像操作传统 Session 一样操作分布式 Session,Spring Session 会自动将 Session 数据存储到 Redis 中。
1. Controller 中操作 Session
1 | import org.springframework.stereotype.Controller; |
2. 验证 Redis 中的 Session 数据
启动应用并访问 http://localhost:8080/login?username=张三,登录成功后,通过 Redis 客户端查看 Session 数据:
1 | 1. 查看所有 Session 相关的键(前缀为 "myapp:session:") |
键含义说明:
myapp:session:sessions:{sessionId}:存储 Session 的核心数据(创建时间、属性、过期时间等);myapp:session:sessions:expires:{sessionId}:用于 Redis 过期监听,确保 Session 过期后及时清理;myapp:session:expirations:{timestamp}:按过期时间分组的 Session ID 集合,用于批量清理过期 Session。
底层原理:Spring Session 如何实现 Session 共享?
核心是 “过滤器拦截→请求包装→Session 委托” 三步流程,以下是简化的原理示意图:
关键组件解析
SessionRepositoryRequestWrapper:- 继承
HttpServletRequestWrapper,重写getSession()和getSession(boolean create)方法; - 当调用
getSession()时,不访问 Web 容器的内存 Session,而是通过SessionRepository从 Redis 读取或创建 Session。
- 继承
RedisOperationsSessionRepository:SessionRepository的 Redis 实现类,负责 Session 的 CRUD 操作;- 序列化 Session 数据并存储到 Redis,同时设置过期时间;
- 监听 Redis 过期事件,当 Session 过期时清理相关数据。
DelegatingFilterProxy:- 作为 “桥梁”,将 Web 容器的过滤器生命周期与 Spring 容器的 Bean 管理结合;
- 确保
springSessionRepositoryFilter能使用 Spring 容器中的RedisConnectionFactory、SessionRepository等组件。
分布式环境验证
为确保 Session 共享,可部署两个应用节点(如端口 8080 和 8081),通过负载均衡(如 Nginx)测试:
1. Nginx 配置(负载均衡)
1 | http { |
2. 测试步骤
- 访问
http://localhost/login?username=张三(Nginx 分发到 8080 节点),登录成功; - 刷新页面或访问
http://localhost/user/info(Nginx 可能分发到 8081 节点); - 结果:仍能获取到
loginUser=张三,说明 Session 在两个节点间共享(数据存储在 Redis 中)。
常见问题与解决方案
1. Session 数据未写入 Redis
- 原因:
- 过滤器
springSessionRepositoryFilter未配置或顺序错误(需在所有过滤器之前); - Redis 连接失败(如地址、端口错误,或 Redis 服务未启动);
- 序列化器配置错误(如
Jackson依赖缺失,导致序列化失败);
- 过滤器
- 解决方案:
- 检查
filter-mapping顺序,确保springSessionRepositoryFilter是第一个过滤器; - 查看应用日志,确认 Redis 连接状态(如
RedisConnectionFailedException); - 确保
jackson-databind依赖已添加,且序列化器配置正确。
- 检查
2. 多节点 Session 不共享
- 原因:
- 不同节点连接的 Redis 实例不同(需连接同一 Redis);
redisNamespace配置不同(导致键前缀不同,无法共享数据);
- 解决方案:
- 所有节点的
redisConnectionFactory配置相同的 Redis 地址; - 确保所有节点的
redisNamespace一致(如统一为myapp:session)。
- 所有节点的
3. Session 过期时间不生效
- 原因:
maxInactiveIntervalInSeconds配置错误(单位是秒,而非分钟);- Redis 未启用过期键功能(需 Redis 版本 ≥ 2.8.0);
- 解决方案:
- 确认
maxInactiveIntervalInSeconds配置(如 30 分钟 = 1800 秒); - 升级 Redis 到 2.8.0 以上版本,或通过
redis-cli config get enable-expire确认过期功能已启用。
- 确认
总结与最佳实践
1. 核心优势
- 无侵入:无需修改业务代码,像操作普通 Session 一样使用;
- 多存储支持:除 Redis 外,还支持 MongoDB、JDBC 等存储;
- 高可用:依赖分布式存储(如 Redis 集群),避免单点故障;
- 扩展性强:支持 Session 事件监听、自定义序列化等高级功能。
2. 最佳实践
- Redis 部署:
- 生产环境使用 Redis 集群(如 Redis Cluster),确保高可用;
- 配置 Redis 持久化(如 RDB + AOF),避免 Session 数据丢失。
- 序列化选择:
- 推荐使用
GenericJackson2JsonRedisSerializer(JSON 格式,可读性高,支持复杂对象); - 避免使用
JdkSerializationRedisSerializer(二进制格式,可读性差,且依赖类的序列化 ID)。
- 推荐使用
- Session 优化:
- 减少 Session 中存储的数据量(仅存储必要信息,如用户 ID、角色);
- 合理设置
maxInactiveIntervalInSeconds(如 30 分钟,避免 Session 长期占用 Redis 资源)
v1.3.10