Java案例如何实现分布式会话?

wen java案例 77

本文目录导读:

Java案例如何实现分布式会话?

  1. 方案一:基于Redis + Spring Session(最推荐)
  2. 方案二:基于Token的JWT(无状态会话,适合前后端分离)
  3. 方案三:基于关系数据库(MySQL)或非关系数据库(MongoDB)
  4. 方案四:基于Spring Session + Cassandra(大型分布式场景)
  5. 总结:如何选择?

在Java中实现分布式会话,核心思路是将用户的会话数据(如登录状态、购物车信息)从单个JVM内存中抽离出来,存储到一个所有应用服务器都能访问的共享外部存储中。

以下是几种主流的实现方案及其Java案例,从常用到深入排序:

基于Redis + Spring Session(最推荐)

这是目前Java生态中最流行、最简洁的方案,Spring Session透明地接管了HttpSession,并将数据存储到Redis中。

原理:通过Filter拦截HttpServletRequest,把getSession()方法返回的自定义Session实现,数据操作都映射到Redis的Hash结构中。

实现步骤:

  1. 引入依赖(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>
  2. 配置application.yml

    spring:
      session:
        store-type: redis # 指定会话存储方式为Redis
      redis:
        host: 192.168.1.100 # 你的Redis服务地址
        port: 6379
  3. 启用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);
        }
    }
  4. 业务代码(直接使用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,服务器解密验证即可。

实现步骤:

  1. 引入依赖(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>
  2. 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; // 校验失败或过期
            }
        }
    }
  3. 登录接口与拦截器

    @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_IDATTRIBUTE_NAMEATTRIBUTE_VALUEEXPIRY_TIME,每次请求时,根据Session ID查询数据库获取属性。

实现(Spring Session + JDBC)

  1. 引入依赖

    <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>
  2. 配置application.yml

    spring:
      session:
        store-type: jdbc # 存储方式改为数据库
      datasource:
        url: jdbc:mysql://localhost:3306/test?useSSL=false
        username: root
        password: root
  3. 创建数据库表:Spring Session提供了默认的建表脚本(位于org/springframework/session/jdbc/schema-*.sql),需要初始化一张SPRING_SESSION表,项目启动时自动创建(或手动执行)。

  4. 业务代码:与方案一完全相同,使用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) 改动最小,只需添加依赖和配置。

最佳实践建议

  1. 安全性:如果使用Redis,建议开启密码认证和TLS加密;如果使用JWT,务必使用HTTPS传输,签名密钥定期更换。
  2. 序列化:在Redis方案中,默认使用JDK序列化(效率低),建议改为JSON序列化(Jackson):
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
  3. 会话过期:分布式环境下,Session过期时间建议与业务强相关,不要依赖默认值(通常30分钟),前端配合心跳机制刷新Token或Session有效期。

方案均提供了完整的Java代码示例,可以根据实际项目需求选择。

抱歉,评论功能暂时关闭!