微服务架构下 Spring Session 与 Redis 分布式会话实战全解析
作者:服务端技术精选
标签:Spring Boot · Spring Session · Redis · 微服务
难度:中级
前言
你是否遇到过这样的场景:
- 用户在服务 A 登录成功,跳转到服务 B 时却提示未登录
- 多个服务部署在不同服务器,用户刷新页面后 Session 丢失
- 水平扩展后,新增的服务器无法访问用户 Session
- 单点登录(SSO)需求,需要跨系统共享登录状态
这些问题在单体应用中不存在,但在微服务架构中却是常见痛点。传统的 HTTP Session 存储在服务器内存中,无法在多个服务之间共享。
今天要介绍的「Spring Session + Redis 分布式会话」方案,将彻底解决这个问题——多服务共享会话,水平扩展无障碍。
一、传统会话的痛点
场景重现
你的系统从单体应用拆分为微服务架构:
单体应用:
┌─────────────────────────────────┐
│ Nginx │
│ ┌───────────────────────┐ │
│ │ 单体应用 │ │
│ │ (Session 存在内存) │ │
│ └───────────────────────┘ │
└─────────────────────────────────┘
│
▼
用户登录成功
拆分为微服务后:
微服务架构:
┌─────────────────────────────────┐
│ Nginx │
│ ┌────────┬────────┬────────┐│
│ │服务 A │服务 B │服务 C ││
│ │Session │Session │Session ││
│ │(内存) │(内存) │(内存) ││
│ └────────┴────────┴────────┘│
└─────────────────────────────────┘
│
▼
用户在服务 A 登录
跳转到服务 B → 未登录!
传统方案的三大痛点
| 痛点 | 描述 | 影响 |
|---|---|---|
| 会话不一致 | 每个服务维护自己的 Session | 用户跨服务需要重新登录 |
| 无法水平扩展 | Session 存储在内存中 | 新增服务器无法访问已有 Session |
| 单点故障 | 服务器宕机导致 Session 丢失 | 用户体验极差 |
| SSO 困难 | 跨系统无法共享登录状态 | 需要复杂的 Token 机制 |
更糟糕的是:有些系统通过 Session 复制或 Sticky Session 解决问题,但都存在性能瓶颈或单点故障风险。
二、Spring Session:会话存储与容器解耦
核心思想
Spring Session 的核心思想是:将会话存储与 Servlet 容器解耦,通过拦截器模式替换默认的会话管理器,实现存储后端的无缝切换。
┌─────────────────────────────────────────────────────────┐
│ Spring Session 架构 │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ HTTP 请求 │────────▶│ 拦截器 │ │
│ └─────────────┘ └─────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Session │◀────────│ Spring │ │
│ │ Repository │ 读写 │ Session │ │
│ └─────────────┘ └─────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Redis │ │ 其他存储 │ │
│ │ (默认) │ │ (可选) │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
技术选型
| 组件 | 作用 | 优势 |
|---|---|---|
| Spring Session | 会话管理框架 | 与 Spring 生态无缝集成 |
| Redis | 会话存储 | 高性能、支持集群、数据持久化 |
| Spring Boot | 应用框架 | 简化配置、快速开发 |
三、实现方案详解
1. 添加依赖
<dependencies>
<!-- Spring Session Data Redis -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- Spring Data 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>
2. 配置 Redis
spring:
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 5000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
session:
store-type: redis
redis:
namespace: spring:session
flush-mode: on_save
cleanup-cron: "0 * * * * * *"
3. 启用 Spring Session
@SpringBootApplication
@EnableRedisHttpSession(
maxInactiveIntervalInSeconds = 1800, // 30 分钟过期
redisNamespace = "spring:session"
)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4. 用户登录与会话管理
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@PostMapping("/login")
public Map<String, Object> login(
@RequestParam String username,
@RequestParam String password,
HttpSession session) {
// 验证用户名密码
if (authenticate(username, password)) {
// 将用户信息存入 Session
session.setAttribute("user", getUser(username));
session.setAttribute("loginTime", LocalDateTime.now());
return Map.of(
"success", true,
"message", "登录成功",
"sessionId", session.getId()
);
} else {
return Map.of(
"success", false,
"message", "用户名或密码错误"
);
}
}
@GetMapping("/logout")
public Map<String, Object> logout(HttpSession session) {
session.invalidate();
return Map.of(
"success", true,
"message", "退出成功"
);
}
@GetMapping("/current")
public Map<String, Object> getCurrentUser(HttpSession session) {
User user = (User) session.getAttribute("user");
if (user == null) {
return Map.of(
"success", false,
"message", "未登录"
);
}
return Map.of(
"success", true,
"user", user,
"loginTime", session.getAttribute("loginTime")
);
}
}
5. 多服务共享会话
服务 A 配置:
spring:
application:
name: service-a
redis:
host: localhost
port: 6379
session:
store-type: redis
服务 B 配置:
spring:
application:
name: service-b
redis:
host: localhost
port: 6379
session:
store-type: redis
关键点:
- 两个服务连接同一个 Redis
- 使用相同的 Redis namespace
- Session 自动在两个服务间共享
四、实战演示
场景一:用户登录与跨服务访问
步骤 1:在服务 A 登录
POST http://localhost:8081/api/auth/login
Content-Type: application/x-www-form-urlencoded
username=admin&password=123456
响应:
{
"success": true,
"message": "登录成功",
"sessionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
步骤 2:访问服务 A 的受保护资源
GET http://localhost:8081/api/auth/current
Cookie: JSESSIONID=a1b2c3d4-e5f6-7890-abcd-ef1234567890
响应:
{
"success": true,
"user": {
"id": 1,
"username": "admin",
"name": "管理员"
},
"loginTime": "2024-01-15T10:00:00"
}
步骤 3:访问服务 B 的受保护资源
GET http://localhost:8082/api/auth/current
Cookie: JSESSIONID=a1b2c3d4-e5f6-7890-abcd-ef1234567890
响应:
{
"success": true,
"user": {
"id": 1,
"username": "admin",
"name": "管理员"
},
"loginTime": "2024-01-15T10:00:00"
}
关键点: 使用相同的 Session ID,服务 B 也能获取到用户信息!
场景二:水平扩展
部署多个服务实例:
┌─────────────────────────────────┐
│ Nginx │
│ ┌────────┬────────┬────────┐│
│ │服务 A1 │服务 A2 │服务 A3 ││
│ │:8081 │:8082 │:8083 ││
│ └────────┴────────┴────────┘│
│ │ │
│ ▼ ▼
│ ┌───────────────────────┐ │
│ │ Redis │ │
│ │ (共享 Session) │ │
│ └───────────────────────┘ │
└─────────────────────────────────┘
Nginx 配置:
upstream service_a {
server localhost:8081;
server localhost:8082;
server localhost:8083;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://service_a;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
效果:
- 用户请求被分发到不同的服务实例
- 所有实例共享同一个 Redis 中的 Session
- 用户刷新页面或重启服务,Session 不会丢失
五、进阶功能
1. 自定义 Session 过期策略
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SessionConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
2. Session 事件监听
@Component
@Slf4j
public class SessionEventListener {
@EventListener
public void onSessionCreated(SessionCreatedEvent event) {
String sessionId = event.getSessionId();
log.info("Session 创建: {}", sessionId);
}
@EventListener
public void onSessionDestroyed(SessionDestroyedEvent event) {
String sessionId = event.getSessionId();
log.info("Session 销毁: {}", sessionId);
}
@EventListener
public void onSessionExpired(SessionExpiredEvent event) {
String sessionId = event.getSessionId();
log.info("Session 过期: {}", sessionId);
}
}
3. Session 数据清理
@Configuration
@EnableRedisHttpSession(
maxInactiveIntervalInSeconds = 1800,
cleanupCron = "0 0 * * * * *" // 每小时清理一次
)
public class SessionConfig {
// ...
}
4. 多租户 Session 隔离
@Configuration
public class MultiTenantSessionConfig {
@Bean
public RedisHttpSessionConfiguration redisHttpSessionConfiguration() {
RedisHttpSessionConfiguration config = new RedisHttpSessionConfiguration();
config.setRedisNamespace("spring:session:" + TenantContext.getTenantId());
return config;
}
}
六、最佳实践
1. Session 存储结构
Redis 中的 Session 存储结构:
spring:session:sessions:<sessionId>
├── lastAccessedTime: 1705276800000
├── creationTime: 1705276800000
├── maxInactiveInterval: 1800
├── sessionAttr:user: {"id":1,"username":"admin"}
├── sessionAttr:loginTime: "2024-01-15T10:00:00"
└── ...
spring:session:expirations:<timestamp>
└── <sessionId>: ""
2. Session 过期策略
| 策略 | 适用场景 | 优缺点 |
|---|---|---|
| 固定时间过期 | 银行、金融等安全要求高的场景 | 优点:安全性高;缺点:用户体验差 |
| 滑动过期 | 电商、社交等需要长时间在线的场景 | 优点:用户体验好;缺点:安全性相对较低 |
| 混合策略 | 大多数业务场景 | 优点:平衡安全性和体验;缺点:实现复杂 |
3. 性能优化
| 优化点 | 方案 | 效果 |
|---|---|---|
| 连接池配置 | 合理配置 Redis 连接池 | 提升并发性能 |
| 序列化优化 | 使用 JSON 序列化 | 减少序列化开销 |
| Session 大小控制 | 避免在 Session 中存储大量数据 | 减少网络传输 |
| Redis 集群 | 使用 Redis Cluster | 提升可用性和扩展性 |
4. 安全最佳实践
| 安全措施 | 方案 |
|---|---|
| Session 固定 | 检测 Session ID 是否被篡改 |
| HTTPS 传输 | 防止 Session ID 被窃取 |
| HttpOnly Cookie | 防止 XSS 攻击窃取 Session |
| SameSite Cookie | 防止 CSRF 攻击 |
| 定期刷新 Session ID | 防止 Session 劫持 |
七、常见问题
Q1: Session 数据过大怎么办?
A: 可以考虑:
- 减少存储在 Session 中的数据
- 使用 Redis Hash 存储大数据
- 将大数据存储到数据库,Session 只存储引用
Q2: 如何实现单点登录(SSO)?
A: 可以:
- 使用共享的 Redis 存储 Session
- 实现统一的认证中心
- 使用 JWT Token 替代 Session
Q3: Redis 宕机怎么办?
A: 可以:
- 使用 Redis Sentinel 实现高可用
- 使用 Redis Cluster 实现分布式存储
- 实现本地 Session 缓存作为降级方案
Q4: 如何监控 Session 使用情况?
A: 可以:
- 使用 Redis 的 INFO 命令查看 Session 数量
- 实现 Session 事件监听,统计 Session 创建和销毁
- 使用 APM 工具监控 Session 性能
八、总结
Spring Session + Redis 分布式会话方案,彻底解决了微服务架构下的会话管理问题:
✅ 会话共享:多服务之间共享 Session
✅ 水平扩展:新增服务器无障碍
✅ 高可用:Redis 集群保证可用性
✅ 易于集成:与 Spring 生态无缝集成
✅ 灵活配置:支持多种存储后端
让微服务架构下的会话管理变得简单、可靠、高效!
互动话题
你的项目中是如何处理分布式会话的?有没有遇到过 Session 不一致的问题?欢迎在评论区分享你的经验。
更多技术文章,欢迎关注公众号服务端技术精选,及时获取最新动态。
标题:微服务架构下 Spring Session 与 Redis 分布式会话实战全解析
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/03/16/1773468730289.html
公众号:服务端技术精选
- 前言
- 一、传统会话的痛点
- 场景重现
- 传统方案的三大痛点
- 二、Spring Session:会话存储与容器解耦
- 核心思想
- 技术选型
- 三、实现方案详解
- 1. 添加依赖
- 2. 配置 Redis
- 3. 启用 Spring Session
- 4. 用户登录与会话管理
- 5. 多服务共享会话
- 四、实战演示
- 场景一:用户登录与跨服务访问
- 场景二:水平扩展
- 五、进阶功能
- 1. 自定义 Session 过期策略
- 2. Session 事件监听
- 3. Session 数据清理
- 4. 多租户 Session 隔离
- 六、最佳实践
- 1. Session 存储结构
- 2. Session 过期策略
- 3. 性能优化
- 4. 安全最佳实践
- 七、常见问题
- Q1: Session 数据过大怎么办?
- Q2: 如何实现单点登录(SSO)?
- Q3: Redis 宕机怎么办?
- Q4: 如何监控 Session 使用情况?
- 八、总结
- 互动话题
评论