微服务架构下 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: 可以考虑:

  1. 减少存储在 Session 中的数据
  2. 使用 Redis Hash 存储大数据
  3. 将大数据存储到数据库,Session 只存储引用

Q2: 如何实现单点登录(SSO)?

A: 可以:

  1. 使用共享的 Redis 存储 Session
  2. 实现统一的认证中心
  3. 使用 JWT Token 替代 Session

Q3: Redis 宕机怎么办?

A: 可以:

  1. 使用 Redis Sentinel 实现高可用
  2. 使用 Redis Cluster 实现分布式存储
  3. 实现本地 Session 缓存作为降级方案

Q4: 如何监控 Session 使用情况?

A: 可以:

  1. 使用 Redis 的 INFO 命令查看 Session 数量
  2. 实现 Session 事件监听,统计 Session 创建和销毁
  3. 使用 APM 工具监控 Session 性能

八、总结

Spring Session + Redis 分布式会话方案,彻底解决了微服务架构下的会话管理问题:

会话共享:多服务之间共享 Session
水平扩展:新增服务器无障碍
高可用:Redis 集群保证可用性
易于集成:与 Spring 生态无缝集成
灵活配置:支持多种存储后端

让微服务架构下的会话管理变得简单、可靠、高效!


互动话题

你的项目中是如何处理分布式会话的?有没有遇到过 Session 不一致的问题?欢迎在评论区分享你的经验。


更多技术文章,欢迎关注公众号服务端技术精选,及时获取最新动态。


标题:微服务架构下 Spring Session 与 Redis 分布式会话实战全解析
作者:jiangyi
地址:http://jiangyi.space/articles/2026/03/16/1773468730289.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消