本文目录导读:

- 方案一:基于Redis + Spring Session(最推荐)
- 方案二:基于Token的JWT(无状态会话,适合前后端分离)
- 方案三:基于关系数据库(MySQL)或非关系数据库(MongoDB)
- 方案四:基于Spring Session + Cassandra(大型分布式场景)
- 总结:如何选择?
在Java中实现分布式会话,核心思路是将用户的会话数据(如登录状态、购物车信息)从单个JVM内存中抽离出来,存储到一个所有应用服务器都能访问的共享外部存储中。
以下是几种主流的实现方案及其Java案例,从常用到深入排序:
基于Redis + Spring Session(最推荐)
这是目前Java生态中最流行、最简洁的方案,Spring Session透明地接管了HttpSession,并将数据存储到Redis中。
原理:通过Filter拦截HttpServletRequest,把getSession()方法返回的自定义Session实现,数据操作都映射到Redis的Hash结构中。
实现步骤:
-
引入依赖(Maven):
<!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> -
配置application.yml:
spring: session: store-type: redis # 指定会话存储方式为Redis redis: host: 192.168.1.100 # 你的Redis服务地址 port: 6379 -
启用Spring Session(启动类添加注解):
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; @SpringBootApplication @EnableRedisHttpSession // 启用Redis会话管理 public class DistributedSessionApplication { public static void main(String[] args) { SpringApplication.run(DistributedSessionApplication.class, args); } } -
业务代码(直接使用HttpSession):无需任何修改,Spring Session会自动重定向会话逻辑。
@RestController public class UserController { @PostMapping("/login") public String login(HttpSession session, @RequestParam String username) { // 存到Redis中,而不是本机内存 session.setAttribute("user", username); return "登录成功,SessionID: " + session.getId(); } @GetMapping("/info") public String getInfo(HttpSession session) { // 无论请求打到A服务还是B服务,都能从Redis取到 String user = (String) session.getAttribute("user"); return user != null ? "当前用户: " + user : "未登录"; } }
优点:零侵入、配置简单、序列化自动处理、支持集群会话事件。
缺点:需要维护Redis集群(但通常企业环境已有)。
基于Token的JWT(无状态会话,适合前后端分离)
这是一种彻底的无状态方案,服务器不存储任何Session数据,客户端(浏览器、APP)自行持有Token凭证。
原理:用户登录后,服务器生成一个加密的JSON Web Token(包含用户ID、过期时间、签名),客户端存储在LocalStorage或Header中,每次请求携带该Token,服务器解密验证即可。
实现步骤:
-
引入依赖(Maven):
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.12.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.12.5</version> <scope>runtime</scope> </dependency> -
JWT工具类:
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import javax.crypto.SecretKey; import java.util.Date; public class JwtUtil { private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor("mySecretKey1234567890mySecretKey1234567890".getBytes()); private static final long EXPIRATION = 30 * 60 * 1000; // 30分钟 public static String generateToken(String userId) { return Jwts.builder() .subject(userId) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + EXPIRATION)) .signWith(SECRET_KEY) .compact(); } public static String parseToken(String token) { try { return Jwts.parser() .verifyWith(SECRET_KEY) .build() .parseSignedClaims(token) .getPayload() .getSubject(); } catch (Exception e) { return null; // 校验失败或过期 } } } -
登录接口与拦截器:
@RestController public class AuthController { @PostMapping("/jwt/login") public String login(@RequestParam String username, @RequestParam String password) { // 模拟验证用户名密码 if ("admin".equals(username) && "123".equals(password)) { String token = JwtUtil.generateToken(username); return "登录成功,Token: " + token; } return "登录失败"; } @GetMapping("/jwt/info") public String getInfo(@RequestHeader("Authorization") String token) { String userId = JwtUtil.parseToken(token.replace("Bearer ", "")); if (userId == null) { return "Token无效或过期"; } return "当前用户: " + userId; } }
优点:完全无状态,无需共享存储,天然支持分布式,扩展性好。
缺点:Token一旦签发无法主动失效(除非设置短过期时间或维护黑名单),Token体积较大,适用于纯API、前后端分离或移动端应用。
基于关系数据库(MySQL)或非关系数据库(MongoDB)
如果团队对Redis不熟悉或已有数据库集群,也可以使用数据库存储Session。
原理:定义一个SESSION表,包含SESSION_ID、ATTRIBUTE_NAME、ATTRIBUTE_VALUE、EXPIRY_TIME,每次请求时,根据Session ID查询数据库获取属性。
实现(Spring Session + JDBC):
-
引入依赖:
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> -
配置application.yml:
spring: session: store-type: jdbc # 存储方式改为数据库 datasource: url: jdbc:mysql://localhost:3306/test?useSSL=false username: root password: root -
创建数据库表:Spring Session提供了默认的建表脚本(位于
org/springframework/session/jdbc/schema-*.sql),需要初始化一张SPRING_SESSION表,项目启动时自动创建(或手动执行)。 -
业务代码:与方案一完全相同,使用
HttpSession即可。
优点:利用现有数据库基础设施,事务性强。
缺点:数据库IO性能低于Redis,不适合高并发;需要清理过期会话。
基于Spring Session + Cassandra(大型分布式场景)
对于超大规模集群,可以使用NoSQL中的Cassandra,Spring Session同样提供支持。
依赖:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-cassandra</artifactId>
</dependency>
配置与Redis类似,只是store-type改为cassandra。
如何选择?
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| Java微服务集群 | Redis + Spring Session | 最成熟,性能好,代码零侵入。 |
| 前端分离/移动端/APP | JWT Token | 无状态,跨域友好,后端无需维护Session。 |
| 已有MySQL集群,无Redis | JDBC Session | 不需要额外中间件,但注意性能瓶颈。 |
| 超大规模(亿级用户) | Redis Cluster + 自定义Session(或Cassandra) | 需要更高的扩展性与容错性。 |
| 遗留系统改造 | 方案一(嵌入式Filter) | 改动最小,只需添加依赖和配置。 |
最佳实践建议:
- 安全性:如果使用Redis,建议开启密码认证和TLS加密;如果使用JWT,务必使用HTTPS传输,签名密钥定期更换。
- 序列化:在Redis方案中,默认使用JDK序列化(效率低),建议改为JSON序列化(Jackson):
@Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { return new GenericJackson2JsonRedisSerializer(); } - 会话过期:分布式环境下,Session过期时间建议与业务强相关,不要依赖默认值(通常30分钟),前端配合心跳机制刷新Token或Session有效期。
方案均提供了完整的Java代码示例,可以根据实际项目需求选择。