如何设计一个分布式Session?

2025年 阅读约 15 分钟 面试指南 · 系统设计

深入解析分布式Session方案:Session粘滞(Nginx ip_hash)、Session复制(Tomcat集群)、集中存储(Redis+Spring Session)、JWT无状态方案对比,附面试模拟问答。

一句话总结

分布式Session方案对比:Session粘滞(Nginx ip_hash,简单但负载不均)→ Session复制(Tomcat集群广播,性能差)→ 集中存储(Redis + Spring Session,最常用)→ JWT无状态(客户端存储,彻底无状态,但无法主动失效)。生产推荐:Redis集中存储(Spring Session)或 JWT(微服务场景)。核心区别:Session是有状态的(服务端存),JWT是无状态的(客户端存)。

初级理解

为什么需要分布式Session?

# 问题:单体 → 分布式 # 单体应用:Session 存在本地内存,没问题 # 分布式:请求被负载均衡到不同服务器 # 用户登录在 Server A,下次请求到 Server B → 未登录! # 解决方案 # 1. Session 粘滞:同一用户始终路由到同一台服务器 # 2. Session 复制:服务器间同步 Session # 3. 集中存储:Session 存 Redis/DB # 4. 无状态:JWT Token,客户端存储

四种方案对比

方案原理优点缺点
Session粘滞Nginx ip_hash 固定路由简单,不改代码负载不均,服务器宕机丢失
Session复制Tomcat 集群广播不依赖外部存储性能差,网络开销大
集中存储Redis 存 Session可靠,性能好依赖 Redis
JWT客户端存 Token无状态,易扩展无法主动失效
一句话总结:Session粘滞简单但不可靠;集中存储(Redis)最常用;JWT适合微服务。

中级深入

Redis 集中存储 — Spring Session

# Spring Session + Redis 配置 spring: session: store-type: redis timeout: 1800 # 30 分钟过期 redis: host: localhost port: 6379 # 使用(和普通 Session 一样) @RestController public class UserController { @GetMapping("/login") public String login(HttpSession session) { session.setAttribute("userId", 1001); return "ok"; } @GetMapping("/info") public String info(HttpSession session) { Long userId = (Long) session.getAttribute("userId"); return "userId: " + userId; } } # Spring Session 原理 # 1. SessionRepositoryFilter 拦截请求 # 2. 包装 HttpServletRequest → SessionRepositoryRequestWrapper # 3. getSession() 从 Redis 读写 Session # 4. 每次请求结束自动刷新 Redis 中的 Session

JWT 无状态方案

# JWT 结构:Header.Payload.Signature # Header: {"alg": "HS256", "typ": "JWT"} # Payload: {"userId": 1001, "exp": 1700000000} # Signature: HMACSHA256(base64(header) + "." + base64(payload), secret) # 生成 JWT String jwt = Jwts.builder() .setSubject("1001") # userId .setExpiration(new Date(...)) # 过期时间 .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); # 验证 JWT Claims claims = Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); String userId = claims.getSubject(); # JWT 优缺点 # 优点:无状态、易扩展、跨域友好 # 缺点:无法主动失效(只能等过期)、payload 不能太大
中级要点:Spring Session 自动管理 Redis Session;JWT 无状态但无法主动失效。

高级拓展

Session 共享的安全问题

# 1. Session 固定攻击 # 攻击者获取一个 Session ID → 诱导用户使用该 ID 登录 # 解决:登录成功后重新生成 Session ID session.invalidate(); session = request.getSession(true); # 2. Session 劫持 # 攻击者窃取 Session ID(XSS、网络嗅探) # 解决:HTTPS + HttpOnly Cookie + Secure Cookie # 3. CSRF 跨站请求伪造 # 攻击者诱导用户点击链接,利用已登录 Session # 解决:CSRF Token + SameSite Cookie # 4. Redis 集中存储的安全 # Redis 设置密码 + 内网访问 # Session 数据加密存储(敏感信息)

JWT 主动失效方案

# JWT 无法主动失效的解决方案 # 方案1:黑名单(Redis) # 需要失效时,将 JWT 加入 Redis 黑名单 # 验证时先查黑名单 SET "jwt:blacklist:xxx" 1 EX 3600 # 方案2:版本号 # 用户表加 token_version 字段 # 修改密码/登出时 version++ # JWT payload 中包含 version # 验证时比较 JWT version 和 DB version # 方案3:短有效期 + Refresh Token # Access Token:15 分钟(短) # Refresh Token:7 天(长,存 Redis) # Access Token 过期后用 Refresh Token 刷新

实战场景

场景:微服务认证方案

# 推荐方案:JWT + Redis 黑名单 # 1. 登录 → 返回 Access Token(15min)+ Refresh Token(7天) # 2. 请求携带 Access Token # 3. 网关验证 JWT + 查黑名单 # 4. Access Token 过期 → 用 Refresh Token 刷新 # 5. 登出 → Access Token 加入黑名单 # 网关验证拦截器 public class AuthInterceptor { public boolean preHandle(HttpServletRequest request) { String token = request.getHeader("Authorization"); # 1. 验证 JWT 签名和过期 Claims claims = JwtUtil.parse(token); # 2. 查黑名单 if (redis.hasKey("jwt:blacklist:" + token)) { return false; } # 3. 设置用户上下文 UserContext.set(claims.getSubject()); return true; } }

面试模拟

面试官:分布式Session怎么解决?

你:四种方案:1)Session粘滞(Nginx ip_hash),简单但负载不均;2)Session复制(Tomcat集群),性能差;3)集中存储(Redis + Spring Session),最常用;4)JWT无状态,适合微服务。推荐Redis集中存储或JWT。

面试官:JWT 怎么实现主动失效?

你:JWT本身无法主动失效,但可以通过以下方式实现:1)Redis黑名单:需要失效时加入黑名单,验证时先查黑名单;2)版本号:用户表加token_version,修改密码时递增;3)短有效期+Refresh Token:Access Token 15分钟,Refresh Token存Redis可主动删除。