Redis 分布式 Session 工作原理
1. 传统 Session 的问题
在传统单服务器环境中,HTTP Session 存储在应用服务器的内存中。这在分布式系统中会导致问题:
- 用户的请求可能被分发到不同服务器,导致会话不一致
- 服务器宕机会导致会话丢失
- 需要依赖负载均衡器的会话粘性(sticky session)机制,限制了系统的伸缩性
2. Spring Session + Redis 解决方案
当配置了Spring Session + Redis后,系统会:
-
拦截请求处理流程:
- Spring Session 使用一个过滤器(
SpringSessionRepositoryFilter
)拦截所有HTTP请求 - 这个过滤器由
DelegatingFilterProxy
代理,在Spring Boot应用中自动配置
- Spring Session 使用一个过滤器(
-
替换原生Session实现:
- 过滤器会将容器原生的
HttpSession
实现替换为Spring自定义的实现 - 所有对
HttpSession
的操作实际上都被Spring Session接管
- 过滤器会将容器原生的
-
Session数据外部化:
- 所有会话数据存储在Redis中,而非应用服务器内存
- 客户端仍然通过Cookie接收会话ID
- 服务器使用该ID从Redis中检索完整的会话数据
3. Redis 存储格式
Redis中的几个关键数据结构:
- Strings类型:
spring:session:sessions:expires:{sessionId}
- 用于跟踪会话过期 - Hash表类型:
spring:session:sessions:{sessionId}
- 存储实际的会话数据 - Sets类型:
spring:session:expirations:{timestamp}
- 用于管理会话的过期机制
4. 请求处理流程
-
请求到达:
- 客户端发送请求,包含sessionID的Cookie
- Spring Session过滤器拦截请求
-
会话检索:
- 从Cookie中提取sessionID
- 使用该ID从Redis查询会话数据
- 创建包含该数据的会话对象
-
请求处理:
- 应用程序使用该会话对象,仿佛它是标准的HttpSession
- 对会话的任何修改都被跟踪
-
请求完成:
- 如果会话数据被修改,更新存储在Redis中的数据
- 如有必要,更新过期时间
5. 技术亮点
- 无侵入性:应用代码不需要任何改变,正常使用HttpSession接口
- 无需会话粘性:负载均衡器不再需要配置sticky session
- 弹性伸缩:服务器可以随时添加或移除,不影响用户会话
- 容错性:单个服务器故障不会导致用户会话丢失
- 集中管理:会话数据集中存储,便于监控和管理
这种设计使得Spring应用可以在分布式环境中无缝扩展,同时保持用户会话的连续性和一致性,是微服务架构中解决用户状态管理的优雅方案。
- 客户端先 带着 HTTP 请求到 负载均衡器
- 负载均衡器 转发请求到应用服务器
- 被SpringSessionFiliter 拦截
- 从 Cookie 中提取 SessionID
-
如果 该sessionID 存在,通过 该sessionID 从
SessionRespository
获取会话, SessionRespository 查询 Redis 中的 会话数量,RedisOperations
给Redis 服务器发送了GET spring:session:sessions:{sessionId}
。Redis 给 RedisOperations 返回数据。最终在SessionRespository
构建请求对象给SpringSessionFiliter
。 -
如果 该sessionID 不存在或者过期,那么
SpringSessionFiliter
给 SessionRepository 创建新的会话。sessionRepository 在 Redis 中创建新会话,- Strings类型:
spring:session:sessions:expires:{sessionId}
- 用于跟踪会话过期 - Hash表类型:
spring:session:sessions:{sessionId}
- 存储实际的会话数据 - Sets类型:
spring:session:expirations:{timestamp}
- 用于管理会话的过期机制
创建完成后,返回 新的session 对象给
SpringSessionFiliter
。 - Strings类型:
-
业务处理中可能发生修改 会话数量,如果有 SpringSessionFiliter 调用 SessionRepository
保存会话的变更,更新Redis 中的会话数量, HMSET 更新 会话数量,重置过期时间。
Mermind 绘图代码:
sequenceDiagram
participant C as 客户端
participant LB as 负载均衡器
participant F as SpringSessionFilter
participant S as SessionRepository
participant R as RedisOperations
participant RD as Redis服务器
C->>LB: 1. HTTP请求(带SessionID Cookie)
LB->>F: 2. 转发请求到应用服务器
F->>F: 3. 拦截请求
F->>F: 4. 从Cookie中提取SessionID
alt 存在SessionID
F->>S: 5a. 通过SessionID获取会话
S->>R: 6a. 查询Redis中的会话数据
R->>RD: 7a. GET spring:session:sessions:{sessionId}
RD-->>R: 8a. 返回会话数据
R-->>S: 9a. 返回会话数据
S-->>F: 10a. 构建Session对象
else 不存在SessionID或会话已过期
F->>S: 5b. 创建新会话
S->>R: 6b. 在Redis中创建新会话
R->>RD: 7b. HMSET spring:session:sessions:{newId}
R->>RD: 8b. SET spring:session:sessions:expires:{newId}
R->>RD: 9b. SADD spring:session:expirations:{expireTime}
RD-->>R: 10b. 确认创建成功
R-->>S: 11b. 返回新会话ID
S-->>F: 12b. 返回新Session对象
end
F->>+S: 13. 包装为SessionRepositoryRequestWrapper
S->>-F: 14. 返回包装后的请求对象
F->>F: 15. 继续请求处理链
Note over F,S: 业务逻辑处理期间可能会修改会话数据
F->>S: 16. 保存会话变更(如有)
S->>R: 17. 更新Redis中的会话数据
R->>RD: 18. HMSET 更新会话数据
R->>RD: 19. 重置过期时间
F->>LB: 20. 返回HTTP响应
LB->>C: 21. 转发响应