杰瑞科技汇

Redis如何实现Java Session共享?

为什么需要Redis共享Session?

在传统的单体应用中,用户的所有请求通常都会被负载均衡器(如Nginx)转发到同一台服务器(JVM实例),Session数据可以直接存储在该服务器的内存中,没有问题。

Redis如何实现Java Session共享?-图1
(图片来源网络,侵删)

但在分布式系统微服务架构中,情况发生了变化:

  1. 负载均衡:用户的请求可能被转发到集群中的任何一台服务器。
  2. 无状态服务:为了水平扩展,我们通常希望服务是无状态的,即不依赖本地存储的数据。
  3. 会话不一致:如果用户第一次请求到达了服务器A,Session信息存在A的内存中;下一次请求被转发到了服务器B,B上没有这个用户的Session,用户就需要重新登录,体验极差。

Redis Session共享正是为了解决这个问题,它将Session数据从各个服务器的本地内存中剥离出来,统一存储在一个外部的、所有服务节点都能访问的Redis服务器上,这样,无论用户的请求被分发到哪个服务节点,应用都能从Redis中获取到相同的Session数据。


实现步骤(以Spring Boot + Spring Session + Redis为例)

这是目前最主流、最简单、最优雅的实现方式,Spring Session项目专门用于简化Session管理,它提供了与Redis等存储后端的集成。

第1步:环境准备

  1. 安装并启动Redis服务器
    • 你可以从 Redis官网 下载并安装。
    • 或者使用Docker一键启动:docker run -d -p 6379:6379 redis
  2. 创建一个Spring Boot Web项目
    • 确保项目包含 spring-boot-starter-web 依赖。

第2步:添加Maven依赖

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

Redis如何实现Java Session共享?-图2
(图片来源网络,侵删)
<dependencies>
    <!-- Spring Boot Web Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring Session Data Redis -->
    <!-- 这个依赖是核心,它会自动配置Spring Session和Redis的集成 -->
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    <!-- Spring Boot Data Redis Starter -->
    <!-- 用于连接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>

第3步:配置Redis连接

application.propertiesapplication.yml 文件中配置Redis服务器的地址和端口。

application.properties 示例:

# Redis服务器地址
spring.redis.host=localhost
# Redis服务器端口
spring.redis.port=6379
# (可选) 设置密码,如果Redis设置了密码
# spring.redis.password=yourpassword
# (可选) 设置数据库索引,默认是0
# spring.redis.database=0

application.yml 示例:

spring:
  redis:
    host: localhost
    port: 6379
    # password: yourpassword
    # database: 0

第4步:启用Spring Session Redis(最关键的一步)

创建一个配置类,并使用 @EnableRedisHttpSession 注解,这个注解会告诉Spring Boot,你希望使用Redis来存储HTTP Session。

Redis如何实现Java Session共享?-图3
(图片来源网络,侵删)
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@Configuration
@EnableRedisHttpSession // 核心注解,启用基于Redis的HttpSession
public class SessionConfig {
    // 可以在这里配置Session的超时时间等
    // @EnableRedisHttpSession(maxInactiveInSeconds = 1800) // 设置30分钟超时
}

就是这么简单! 加上这个注解后,Spring Session就会接管所有与Session相关的操作。

第5步:创建一个简单的Controller来测试

创建一个Controller,用来设置Session和获取Session。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@RestController
@RequestMapping("/session")
public class SessionController {
    @GetMapping("/set")
    public String setSession(HttpSession session) {
        // 向session中存入一个属性
        session.setAttribute("user", "zhangsan");
        session.setAttribute("loginTime", System.currentTimeMillis());
        return "Session has been set. User: zhangsan";
    }
    @GetMapping("/get")
    public String getSession(HttpSession session) {
        // 从session中获取属性
        String user = (String) session.getAttribute("user");
        Long loginTime = (Long) session.getAttribute("loginTime");
        if (user != null) {
            return "Get Session Success! User: " + user + ", Login Time: " + loginTime;
        } else {
            return "Session is empty or expired.";
        }
    }
    @GetMapping("/invalidate")
    public String invalidateSession(HttpSession session) {
        // 使session失效
        session.invalidate();
        return "Session has been invalidated.";
    }
}

第6步:测试

  1. 启动你的Spring Boot应用。
  2. 使用Postman或浏览器访问 http://localhost:8080/session/set,这个请求会在Redis中创建一个Session。
  3. 再次访问 http://localhost:8080/session/get,你会成功获取到之前设置的Session信息。
  4. 为了验证分布式效果,你可以启动两个完全相同的应用实例(修改端口为8081后启动),并配置一个负载均衡器(如Nginx)将请求轮流分发到8080和8081端口,你会发现,无论访问哪个端口,Session数据都是共享的。

底层原理浅析

当你使用了 @EnableRedisHttpSession 后,Spring Session做了以下几件事:

  1. 替换 HttpSession 实现:Spring Session创建了一个自定义的 SessionRepositoryFilter,这个Filter会拦截所有HTTP请求,并用Spring Session自己实现的 SessionRepository 替换掉Tomcat(或其他Servlet容器)默认的 HttpSession 实现。
  2. Session操作:当你调用 session.setAttribute()session.getAttribute() 时,实际上操作的是Spring Session的 ExpiringSession 对象。
  3. 序列化与存储:Spring Session会将这个 ExpiringSession 对象进行序列化(默认使用的是 JDK序列化,但推荐使用 JSONJDK序列化,后者性能更好),然后将序列化后的字节数据存入Redis。
    • Redis Key:通常是一个以 spring:session:sessions: 为前缀,加上Session ID的字符串。spring:session:sessions:3fa0c5b0-8d3a-4f9c-b8b0-1f2e3d4c5b6a
    • Redis Value:就是序列化后的Session数据。
    • Redis Hash:为了支持过期等功能,Spring Session还会在Redis中使用一个Hash结构来存储Session的元数据,例如过期时间 (spring:session:expirations:3fa0c5b0...)。
  4. Session ID传递:Session ID仍然通过Cookie在浏览器和服务器之间传递,Spring Session会确保这个Cookie在所有服务实例之间是有效的。

高级配置与最佳实践

Session序列化方式

默认的JDK序列化方式存在安全性和跨语言兼容性问题,推荐使用 JSON 序列化。

配置JSON序列化:

创建一个配置类来配置Redis的Template。

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        // 使用StringRedisSerializer
分享:
扫描分享到社交APP
上一篇
下一篇