分布式会话的演进和最佳事件,含springBoot 实现(Java版本)
一、分布式会话的背景
在微服务架构或集群部署环境下,请求可能落在不同的服务器节点,无法再依赖本地内存来维护用户 Session。因此,需要一种跨节点共享 Session 的机制,这就是 分布式会话管理的核心目标。
二、分布式会话的演进历程
1. 单节点会话(原始方案)
- 会话存储在内存中,如 Tomcat 的 HttpSession。
- 缺点:不可扩展、节点故障丢失会话。
2. Session Sticky(会话绑定)
-
做法:使用负载均衡策略(如 Nginx 的
ip_hash
)将同一用户绑定到固定节点。 -
优点:不用共享 Session。
-
缺点:
- 容错差:节点挂了,Session 丢失。
- 扩展性差:负载不均衡。
3. Session 复制(共享内存同步)
-
如 Tomcat 集群通过
DeltaManager
或BackupManager
实现 Session 同步。 -
缺点:
- 性能开销大(尤其写操作多时)。
- 网络带宽压力高。
4. 集中式 Session 存储(核心阶段)
-
统一存储于 Redis、Memcached、数据库等。
-
优点:
- 节点无状态,扩容方便。
- 容错好,支持持久化。
-
常见技术栈:
- Spring Session + Redis
- Shiro + Redis
- 自定义 Filter 拦截 + Redis
-
缺点:
- 读写 Redis 有延迟,需优化缓存与连接池。
5. Token 模式(Stateless Session)
-
不再使用服务器记录状态。会话状态由客户端持有,常见如 JWT(JSON Web Token)。
-
特点:
- 完全无状态,易于扩展。
- 鉴权速度快,无需访问服务器。
-
缺点:
- JWT 不支持撤销。
- Payload 泄漏风险需加密或签名。
- 会话失效处理复杂(需结合 Redis 存 token 黑名单等)。
6. 混合模式(最佳实践)
-
使用 JWT 携带身份信息 + Redis 存储服务端状态(权限、Session 信息等)。
-
优点兼得:
- 前端无状态便于认证传输。
- 服务端掌握可控状态,支持注销、权限变更等。
三、关键技术对比
模式 | 状态位置 | 扩展性 | 容错性 | 性能 | 安全性 | 实时性 |
---|---|---|---|---|---|---|
Sticky Session | 服务端 | 差 | 差 | 好 | 中 | 高 |
Session 复制 | 多服务端 | 差 | 中 | 差 | 中 | 高 |
Redis集中存储 | Redis | 好 | 好 | 中 | 好 | 中 |
JWT | 客户端 | 最好 | 最好 | 高 | 中(需加密) | 高 |
混合方案 | 客户端+Redis | 最佳 | 最佳 | 中上 | 最佳 | 高 |
四、最佳事件(Best Practices)
✅ 实际生产中推荐:
1. Spring Boot 项目:使用 Spring Session + Redis
- 简单集成,Spring 自动替换原生 Session。
- 可配置 session TTL,支持自动刷新、分布式环境可控。
2. 高并发微服务:使用 JWT + Redis 双模式
- JWT 保证 stateless 高性能登录认证。
- Redis 存储 session 黑名单、权限信息,便于集中管理。
3. 安全性要求高:JWT 签名 + 加密 + Redis 控制权限变更
- 对 token 加签、加密(如 RSA)防篡改、防泄露。
- Redis 控制 Token 生命周期、用户禁用、权限升级等事件。
4. 高可用部署:Redis 使用 Sentinel / Cluster + 本地缓存
- Redis 配合本地 Caffeine/Guava 缓存,减轻访问压力。
- Redis 节点使用 Sentinel 做主从切换,确保高可用。
5. 重要业务审计:使用 Session ID 记录登录轨迹、权限行为
- 配合 Kafka/Logstash 进行操作轨迹分析。
五、总结
分布式会话的演进反映了分布式系统对性能、可用性、安全性和扩展性的不断追求。最佳实践通常采用混合模式,兼顾无状态特性与业务控制能力:
推荐组合:JWT(身份标识)+ Redis(状态控制)+ 本地缓存(性能优化)+ Spring Security/Spring Session 集成
六、分布式会话最佳实践之代码实现
Spring Boot + Spring Security + JWT + Redis 的完整分布式会话控制实现
🧱 一、整体架构图
[前端] ⇄ [Spring Boot 接口层 (JWT Auth Filter)] ⇄ [JWT 验签 + Redis 校验] ⇄ [业务接口]
📦 二、Maven 依赖
<!-- Spring Boot -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!-- Spring Security -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency><!-- JWT -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency><!-- Redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
🔐 三、JWT 工具类
@Component
public class JwtUtil {private final String secretKey = "mySecretKey"; // 建议使用 RSA 非对称密钥private final long expiration = 60 * 60 * 1000; // 1小时public String generateToken(String username) {return Jwts.builder().setSubject(username).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + expiration)).signWith(SignatureAlgorithm.HS512, secretKey).compact();}public String getUsername(String token) {return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();}public boolean isTokenValid(String token) {try {Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();return claims.getExpiration().after(new Date());} catch (Exception e) {return false;}}
}
🔁 四、登录接口
@RestController
@RequestMapping("/auth")
public class AuthController {@Autowired private JwtUtil jwtUtil;@Autowired private RedissonClient redissonClient;@PostMapping("/login")public ResponseEntity<?> login(@RequestBody Map<String, String> login) {String username = login.get("username");String password = login.get("password");//这里要写业务代码查询用户进行数据校验 //获取userId if ("admin".equals(username) && "123456".equals(password)) {String userId= "用户ID";String token = jwtUtil.generateToken(username);RBucket<String> bucket = redissonClient.getBucket("TOKEN:" + userId);bucket.set(token, 30, TimeUnit.MINUTES);return ResponseEntity.ok(Map.of("token", token));}return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials");}@PostMapping("/logout")public ResponseEntity<?> logout(HttpServletRequest request) {String token = request.getHeader("Authorization");if (token != null && token.startsWith("Bearer ")) {String jwt = token.substring(7);String userId= jwtUtil.getUsername(jwt);redissonClient.getBucket("TOKEN:" + userId).delete();}return ResponseEntity.ok("Logged out");}
}
🛡️ 五、JWT 过滤器(拦截器)
@Component
public class JwtAuthFilter extends OncePerRequestFilter {@Autowired private JwtUtil jwtUtil;@Autowired private RedissonClient redissonClient;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {String authHeader = request.getHeader("Authorization");if (authHeader != null && authHeader.startsWith("Bearer ")) {String token = authHeader.substring(7);if (jwtUtil.isTokenValid(token)) {String userId = jwtUtil.getUsername(token); RBucket<String> bucket = redissonClient.getBucket("TOKEN:" + userId);String redisToken= bucket.get();boolean exists = bucket.isExists();if (exists && token.equals(redisToken)) {UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(username, null, List.of());SecurityContextHolder.getContext().setAuthentication(authentication);}}}chain.doFilter(request, response);}
}
⚙️ 六、Spring Security 配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {@Autowired private JwtAuthFilter jwtAuthFilter;@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/auth/**").permitAll().anyRequest().authenticated().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}
}
🧪 七、测试方式
-
登录
- POST
/auth/login
,body:{ "username": "admin", "password": "123456" }
- 返回
token
- POST
-
请求业务接口
- GET
/some/api
,在 Header 中加Authorization: Bearer <token>
- GET
-
退出登录
- POST
/auth/logout
,将当前 token 放入 Header
- POST
🧠 八、扩展建议(进阶后续会不断填坑)
功能需求 | 建议方案 |
---|---|
多端登录互踢 | Redis 中存储设备ID、时间戳,旧设备 token 作废 |
Token 黑名单机制 | Redis 设置黑名单,配合 JWT ID(jti) 做验证 |
细粒度权限控制 | 搭配 Spring Security 的 @PreAuthorize 、@Secured 注解使用 |
Token 刷新机制 | 定期发起 refresh token 请求,更新主 token |
Redis 持久化或 Cluster | 开启持久化,使用 Sentinel/Cluster 高可用 |
收工