Spring Session + Redis 使用

1. Cookie Session

由于Http协议是无状态的协议,为了能够记住请求的状态,于是引入了Session和Cookie的机制。

Session是存在于服务器端的,在单体式应用中,它是由Tomcat管理的,存在于Tomcat的内存中。

当我们为了解决分布式场景中的Session共享问题时,引入了Redis,其共享内存,以及支持key自动过期的特性,非常契合Session的特性,我们在企业开发中最常用的也就是这种模式。但是只要你愿意,也可以选择存储在JDBC,Mongo中,这些,Spring都提供了默认的实现,在大多数情况下,我们只需要引入配置即可。

Cookie则是存在于客户端,更方便理解的说法,可以说存在于浏览器。Http协议允许从服务器返回Response时携带一些Cookie,并且同一个域下对Cookie的数量有所限制,之前说过Session的持久化依赖于服务端的策略,而Cookie的持久化则是依赖于本地文件。虽然说Cookie并不常用,但是有一类特殊的Cookie却是我们需要额外关注的,那便是与Session相关的sessionId,他是真正维系客户端和服务端的桥梁。

当服务端往Session中保存一些数据时,Response中自动添加了一个Cookie:JSESSIONID:xxxx,再后续的请求中,浏览器也是自动的带上了这个Cookie,服务端根据Cookie中的JSESSIONID取到了对应的Session。

客户端服务端是通过JSESSIONID进行交互的,并且,添加和携带key为JSESSIONID的Cookie都是Tomcat和浏览器自动帮助我们完成的。

不同浏览器,访问是隔离的,甚至重新打开同一个浏览器,JSESSIONID也是不同的。但是存放在客户端的Cookie还是存在安全问题的,可修改Cookie“骗”过了服务器。

Spring Sessoin使用第三方仓储来实现集群Session管理,也就是常说的分布式Session容器,替换应用容器(如Tomcat的Session容器)。仓储的实现,Spring Session提供了三个实现(redis,mongodb,jdbc),其中Redis使我们最常用的。

2. Spring Session + Redis

添加依赖

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--spring2.0集成redis所需common-pool2-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.4.2</version>
</dependency>

添加Redis配置

spring:
  redis:
    # Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
    database: 0
    host: localhost
    port: 6379
    # 连接密码(默认为空)
    password:
    # 连接超时时间(毫秒)
    timeout: 10000ms
    lettuce:
      pool:
        # 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-active: 8
        # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        max-wait: -1
        # 连接池中的最大空闲连接 默认 8
        max-idle: 8
        # 连接池中的最小空闲连接 默认 0
        min-idle: 0

添加注解@EnableRedisHttpSession,可自定义Cookie的名称,默认为SESSION

@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {

    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
        defaultCookieSerializer.setCookiePath("/");
        defaultCookieSerializer.setCookieName("SESSION_TEST");
        return defaultCookieSerializer;
    }

}

Spring Session的核心流程是在SessionRepositoryFilter的doFilterInternal方法中。

SessionRepositoryFilter的核心操作是用来修改包装请求和响应,负责包装切换HttpSession至Spring Session的请求和响应。每个HttpRequest进入,都会被该Filter包装成切换Session的请求很响应对象

3. Redis 中的 Spring Session

redis中key
spring:session是默认的Redis HttpSession前缀。

类型数据结构TTL
Aspring:session:sessions:bcfc0d30-290b-40c5-bdb9-8303c9449c7dHash35分钟
Bspring:session:expirations:1586512320000Set30分钟
Cspring:session:sessions:expires:bcfc0d30-290b-40c5-bdb9-8303c9449c7dString30分钟

A类型键
将内存中的Session信息序列化到了Redis中。

创建时间creationTime;最后访问时间lastAccessedTime;最大间隔maxInactiveInterval;若有数据存在Session中,也是存在该Hash中,

B类型键
可当作一个桶,存放着这一分钟应当过期的 Session 的 key,具体值是C类型键
时间计算:lastAccessedTime向上取整 + maxInactiveInterval * 1000

后台有一个定时任务去“删除”过期的 key,来补偿 Redis 到期未删除的 key。
具体操作就是取得当前时间的时间戳作为 key,去 redis 中定位到 spring:session:expirations:{当前时间戳} ,这个 set 里面存放的便是所有过期的 key 了。

每次 Session 的续签,需要将旧桶中的数据移除,放到新桶中。前后相隔一分钟访问,会发现桶发生了变化。
当众多用户活跃时,桶的增删和以及 Set 中数据的增删都是很频繁的,并且对应 key 的 ttl 时间也会被更新。

如果只有A和B两个键可能存在并发问题,前后两分钟都进行续签,可能导致后续签的Session放在了前一个续签桶里,导致Session提前失效。

并且在Spring Session 中 A 类型键的过期时间是 35 分钟,这意味着即便 Session 已经过期,我们还是可以在 Redis 中有 5 分钟间隔来操作过期的 Session。于此同时,Spring Session 引入了 C 类型键来作为 Session 的引用。

C类型键
具体作用便是在自身过期后触发 Redis 的 keyspace notifications,keyspace notifications 只会告诉我们哪个键过期了,不会告诉我们内容是什么。关键就在于如果 Session 过期后监听器可能想要访问 Session 的具体内容,然而自身都过期了,还怎么获取内容。

解耦 Session 的存储和过期,并且使得 server 获取到过期通知后可以访问到 Session 真实的值。
对于用户来说,C 类型键过期后,意味着登录失效,而对于服务端而言,真正的过期其实是 A 类型键过期,这中间会有 5 分钟的误差。

总结
Spring Session 使用这B和C类型键来维持Session过期,Redis清除过期key的行为是一个异步行为且是一个低优先级的行为,可能会导致session不被清除。于是引入了专门的expiresKey,来专门负责Session的清除.

Spring Session 是为了严谨而设计了这一套方案,但引入了定时器和很多辅助的键值对,无疑对内存消耗和 cpu 消耗都是一种浪费。如果在生产环境大量使用 Spring Session,最好权衡下相关问题。

参考:
从零开始的Spring Session(一)
从零开始的Spring Session(二)
从零开始的Spring Session(三)
从Spring-Session源码看Session机制的实现细节
Redis键空间通知
spring-session(一)揭秘
spring-session(一)揭秘续篇
Spring Session 官方文档

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当将会话管理从 Tomcat 切换到 Spring Session + Redis 时,有几个注意事项: 1. 配置 Redis:确保已正确配置和启动 Redis 服务器。你需要提供 Redis 的主机名、端口号和认证信息(如果有的话)。 2. 添加 Spring SessionRedis 的依赖:在项目的构建文件中,添加 Spring SessionRedis 的依赖。例如,在 Maven 中,你可以添加以下依赖: ```xml <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> ``` 3. 配置 Spring Session:在 Spring Boot 的配置文件中,添加以下配置来启用 Spring Session 和指定 Redis 的连接信息: ```properties # 启用 Spring Session spring.session.store-type=redis # Redis 连接信息 spring.redis.host=<redis_host> spring.redis.port=<redis_port> spring.redis.password=<redis_password> ``` 4. 在应用中使用会话:在应用中使用 Spring Session 提供的 API 来管理会话。你可以使用 `@EnableRedisHttpSession` 注解启用 Spring Session,并使用 `@Autowired` 注入 `RedisOperationsSessionRepository` 或 `SessionRepositoryFilter`。 5. 确保会话数据正确存储和检索:通过 Spring Session + Redis,会话数据将存储在 Redis 中。确保会话数据能够正确地存储和检索,以及与应用其他部分的交互正常。 6. 集群环境下的同步问题:如果你的应用在多个实例之间共享会话数据,并且使用 Redis 进行存储,需要注意在集群环境下的会话同步问题。你可以使用 Spring Session 提供的其他解决方案,如使用 Redis Pub/Sub 或 Redis Sentinel。 7. 监控和调优:在切换到 Spring Session + Redis 后,你可能需要重新评估和监控应用的性能和资源消耗情况,以确保它能够满足预期的性能需求。 以上是将会话管理从 Tomcat 切换到 Spring Session + Redis 时需要注意的一些事项。根据你的具体情况和需求,可能还需要调整其他相关配置和代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值