杰瑞科技汇

Redis如何实现Java Session共享?

为什么需要 Session 共享?

在传统的单体应用中,用户的所有请求通常由同一个 Tomcat(或其他应用服务器)实例处理,Session 可以直接存储在该实例的内存中,但在分布式架构中,一个用户的请求可能会被负载均衡器分发到不同的服务器节点上。

  • 问题:如果用户第一次请求到达 A 服务器,Session 信息被存储在 A 服务器的内存中,当用户的第二次请求被分发到 B 服务器时,B 服务器无法找到 A 服务器存储的 Session,导致用户需要重新登录,体验非常差。
  • 解决方案:将 Session 数据从各个应用节点的本地内存中剥离出来,存放到一个所有节点都可以访问的共享存储中,当任何节点需要 Session 数据时,都从这个共享存储中读取,Redis 凭借其高性能、丰富的数据结构和持久化能力,成为了实现 Session 共享最主流的选择。

实现步骤详解

下面我们以最主流的 Spring Boot + Spring Session + Redis 的组合为例,讲解如何实现。

环境准备

  1. Java 项目:一个标准的 Spring Boot Web 项目。
  2. Redis 服务器:确保你已经安装并启动了 Redis 服务。

第一步:添加依赖

在你的 pom.xml 文件中,添加以下关键依赖:

<dependencies>
    <!-- Spring Boot Starter Web (包含 Tomcat 等) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring Session Data Redis (核心依赖) -->
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    <!-- Spring Boot Starter Data Redis (用于连接 Redis) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- Lombok (可选,简化代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

第二步:配置 Redis 和 Spring Session

application.propertiesapplication.yml 文件中,配置 Redis 连接信息以及 Spring Session 的相关属性。

application.yml 示例:

# Redis 配置
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    # 如果有密码,取消注释并填写
    # password: yourpassword
    # 如果使用本地 Docker 或其他环境,可能需要指定数据库索引
    database: 0
# Spring Session 配置
spring:
  session:
    # 1. 指定存储类型为 Redis
    store-type: redis
    # 2. 设置 Session 过期时间 (单位: 秒),30 分钟
    timeout: 1800
    # 3. (可选) 设置 Redis 中 Session 的 key 的前缀
    redis:
      namespace: spring:session

第三步:添加 @EnableRedisHttpSession 注解

这是最关键的一步,创建一个配置类(通常就是主启动类),并添加 @EnableRedisHttpSession 注解。

这个注解会做以下几件事:

Redis如何实现Java Session共享?-图1

  1. 为你的应用注册一个 SessionRepositoryFilter 过滤器,这个过滤器会拦截所有的 HTTP 请求,并用 Spring Session 提供的实现替换掉 Tomcat 原生的 HttpSession 实现。
  2. 当你调用 request.getSession() 时,不再是创建或获取 Tomcat 的 Session,而是从 Redis 中创建或获取一个 Spring Session。
  3. 当 Session 被创建、修改或销毁时,Spring Session 会自动同步这些操作到 Redis 中。

主启动类示例:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@SpringBootApplication
// 启用 Redis-based HTTP session 支持
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) // 也可以在这里设置过期时间
public class SessionSharingApplication {
    public static void main(String[] args) {
        SpringApplication.run(SessionSharingApplication.class, args);
    }
}

第四步:在 Controller 中使用 Session

你可以像往常一样在 Controller 中使用 HttpSession,但背后的一切都已经交给了 Redis。

Redis如何实现Java Session共享?-图2

Controller 示例:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import java.util.UUID;
@RestController
public class SessionController {
    @GetMapping("/set-session")
    public String setSession(HttpSession session) {
        // 从 Session 中获取一个属性,如果不存在则存入一个随机 ID
        String sessionId = (String) session.getAttribute("userId");
        if (sessionId == null) {
            sessionId = UUID.randomUUID().toString();
            session.setAttribute("userId", sessionId);
            return "Session attribute 'userId' has been set to: " + sessionId;
        }
        return "Session attribute 'userId' already exists: " + sessionId;
    }
    @GetMapping("/get-session")
    public String getSession(HttpSession session) {
        String sessionId = (String) session.getAttribute("userId");
        if (sessionId == null) {
            return "No 'userId' found in session. Please visit /set-session first.";
        }
        return "Current 'userId' in session is: " + sessionId;
    }
    @GetMapping("/invalidate-session")
    public String invalidateSession(HttpSession session) {
        session.invalidate(); // 销毁 Session,Redis 中的对应数据也会被删除
        return "Session has been invalidated.";
    }
}

工作原理解析

  1. 用户第一次访问 /set-session:

    Redis如何实现Java Session共享?-图3

    • 请求到达应用服务器 A。
    • SessionRepositoryFilter 拦截请求,发现没有 Session ID(例如在 Cookie 中)。
    • 它会创建一个新的 Spring Session,并生成一个唯一的 ID(a1b2c3d4-e5f6...)。
    • 这个新 Session 会被序列化(通常使用 JSON)并存储到 Redis 中,Key 通常是 spring:session:sessions:a1b2c3d4-e5f6...,Value 是 Session 的内容。
    • 一个名为 SESSION 的 Cookie 会被设置到用户的浏览器中,值为这个 Session ID。
    • session.setAttribute("userId", ...) 的操作会被捕获,并更新 Redis 中对应的 Session 数据。
  2. 用户第二次访问 /get-session:

    • 请求可能被负载均衡器分发到应用服务器 B。
    • SessionRepositoryFilter 拦截请求,从浏览器的 SESSION Cookie 中读取到 Session ID a1b2c3d4-e5f6...
    • 它使用这个 ID 去连接 Redis,查找 Key 为 spring:session:sessions:a1b2c3d4-e5f6... 的数据。
    • 从 Redis 中成功读取到 Session 数据,包括之前设置的 userId
    • 服务器 B 成功获取到了 Session 信息,用户的无感登录体验得以实现。

生产环境最佳实践

  1. Session 序列化方式:

    • Spring Session 默认使用 Java 序列化,这种方式效率不高且存在安全风险。
    • 强烈推荐使用 JSON 序列化,你可以配置 Jackson2JsonRedisSerializer

    自定义序列化配置示例:

    import com.fasterxml.jackson.annotation.JsonTypeInfo;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.session.web.http.CookieSerializer;
    import org.springframework.session.web.http.DefaultCookieSerializer;
    @Configuration
    public class RedisSessionConfig {
        @Bean
        public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
            ObjectMapper objectMapper = new ObjectMapper();
            // 确保反序列化时能正确识别类型
            objectMapper.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY
            );
            return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
        }
        // (可选) 自定义 Cookie 序列化器,解决域名/路径问题
        @Bean
        public CookieSerializer cookieSerializer() {
            DefaultCookieSerializer serializer = new DefaultCookieSerializer();
            // 设置 Cookie 的域名,用于多级域名共享
            // serializer.setCookieName("SESSION");
            // serializer.setUseHttpCookieSerializer(true);
            // serializer.setUseBase64Encoding(false);
            // serializer.setCookiePath("/");
            // serializer.setDomainNamePattern("^.+?\\.(\\w+

分享:
扫描分享到社交APP
上一篇
下一篇