上一篇 《分布式 Session 常见的几种解决方案》中提到了 session 集中管理方案,这里将记录具体的方案实现。

方案中使用单独的 session 服务器来集中管理 session,而 session 服务器有多种选择,如 redis、mango、MySQL 等 DB,这里我们选择 redis 来管理 session,然后使用 spring 官方提供的 spring-session 来处理 session 的共享问题。

这也是目前企业开发用的比较多的一种分布式 session 解决方案:spring-session + redis

1. Spring-Session 介绍

Spring 提供了处理分布式session的解决方案——Spring Session。Spring Session 提供了用于管理用户会话的 API 和 实现。

Spring Session 提供了对 redis,mongodb,mysql 等常用的存储库的支持,Spring Session 提供与 HttpSession 的透明整合,这意味着开发人员可以使用 Spring Session 支持的实现切换 HttpSession 实现。
Spring Session Redis 实现分布式 Session - 图1
Spring Session 添加了一个 SessionRepositoryFilter 的过滤器,用来修改包装请求和响应,包装后的请求为 SessionRepositoryRequestWrapper,调用 getSession() 方法的时候实际上就是调用 Spring Session 实现了的 session。

2. 实现分布式 Session 共享

Spring Session 使用非常简单,添加了相关依赖后,直接操作 HttpSession 就可以实现效果。

:::info 实现流程:

  1. 启动运行 Redis 服务;
  2. 引入 Spring-Session 组件
  3. 演示统一程序启动 8081 端口和 8082 端口模拟 2 个服务分布式;
  4. 调用 8081 登录接口 /user/login;
  5. 调用 8081 获取用户信息接口 /user/info;
  6. 调用 8082 获取用户信息接口 /user/info; :::

(1) 运行 redis 容器

  1. $ docker run -d -p 6379:6379 redis:5

(2) 添加 Spring Session 和 Redis 的相关依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.session</groupId>
  7. <artifactId>spring-session-data-redis</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-starter-data-redis</artifactId>
  12. </dependency>

(3) 配置 redis 相关信息

  1. spring:
  2. redis:
  3. # redis库
  4. database: 0
  5. # redis 服务器地址
  6. host: localhost
  7. # redis 端口号
  8. port: 6379
  9. # redis 密码
  10. password:
  11. # session 使用redis存储
  12. session:
  13. store-type: redis
  14. timeout: 3600

(4) 示例代码

  1. @RequestMapping("/user")
  2. @RestController
  3. public class UserController {
  4. @Autowired
  5. private StringRedisTemplate stringRedisTemplate;
  6. @GetMapping("/login")
  7. public String login(@RequestParam String username,
  8. @RequestParam String password,
  9. HttpSession session){
  10. //账号密码正确,登录成功,保存状态
  11. session.setAttribute("login_user", username);
  12. return "登录成功";
  13. }
  14. @GetMapping("/info")
  15. public String info(HttpSession session) {
  16. return "当前登录的是:" + session.getAttribute("login_user");
  17. }

(5) 运行后 Redis 中存储内容

image.png
Redis 中每个 Session 存储了三条信息:

  • login_user:session 在 redis db 下所属的 namespace,可以在 application.yaml 中配置;
  • login_user:sessions:d65c9c19-f681-4393-9e83-a8cc41d6b2c4:Hash 结构,存储 spring-session 的主要内容:
    • hash 结构有 key 和 field,key 为 “Namespace:sessions:SessionID“,该 key 下的 filed 有:
      • sessionAttr:login_user,存入 session 中的键值对
      • maxInactiveInterval
      • createTime
      • lastAccessedTime
    • 这个 key 的过期时间为 Session 的最大过期时间 +5 分钟,如果默认最大过期时间为 30 分钟,则这个 key 的过期时间为 35 分钟;

image.png

  • d65c9c19-f681-4393-9e83-a8cc41d6b2c4 为这个 Session 的 ID,而存储在 Cookie 中的 SessionID 是经过 base64 编码过后的值:ZDY1YzljMTktZjY4MS00MzkzLTllODMtYThjYzQxZDZiMmM0

image.png

  • login_user:sessions:expires:d65c9c19-f681-4393-9e83-a8cc41d6b2c4:String 结构,不存储任何有用的值,只是表示 Session 过期而设置;
    • key 为 “Namespace:sessions:expires:SessionID“,value 为空;
    • 这个 key 在 Redis 中的过期时间即为 Session 的过期时间间隔;
  • login_user:sessions:expirations:1622590980000:Set 结构,用来存储 Session ID,一条 session 有一个 key;
    • 这个 key 中的最后 1622590980000 值是一个时间戳,根据这个 Session 过期时刻滚动至下一分钟而计算得出;

(6) Spring-Session 对 Session 过期事件的处理

Spring-Session 借助于 Redis 实现了 Session 共享,但是 Session 的过期事件仍然需要处理。

Redis 可以监听某个 key 的变化,当 key 发生变化时,可以快速做出相应的处理。

但是 Redis 监听 key 过期,有两种方式:

  • 当访问时发现其过期
  • Redis 后台逐步查找过期 key

虽然当访问 key 时会发现其过期,并产生过期事件,但是无法保证 key 的过期事件抵达后立即生成过期事件。

所以 Spring-Session 为了能够及时的产生 Session 的过期事件,除了保存 session 内容外,还增加了:

  • login_user:sessions:**expires**:d65c9c19-f681-4393-9e83-a8cc41d6b2c4
  • login_user:sessions:**expirations**:1622590980000

Spring-Session 中有个定时任务,每个整分钟都会查询相应的login_user:sessions:**expirations**:整分钟时间戳中的过期 Session ID,然后再访问一次这个 Session ID,即 login_user:sessions:**expires**:SessionID,以便能够让 Redis 及时的产生 key 过期事件,即 Session 过期事件。

参考: https://mp.weixin.qq.com/s/6q1n4yYR1j2c2I-eJ-Y_kA