Redis 做分布式 Session

背景

最近在用SAS做授权服务器,默认授权服务器的session是基于tomcat服务器的,但是如果你的 SAS 部署了多台实例(集群),该怎么办呢?比如用户在 A 机器登录,确认授权时请求落到了 B 机器,B 找不到 Session 就会让用户重新登录。没有 Redis,SAS 很难做集群部署,也无法提供稳定的单点登录(SSO)体验。

需要引入的依赖

xml 复制代码
<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>

将session保存到redis的原理

首先我们要明白一点,session的创建是是依赖HttpServletRequest.getSession(),但是直接调用就是创建本地session,所以框架就必须偷梁换柱。Spring Session就是利用了 Filter 模式,通过装饰器模式(Decorator Pattern)包装了原始的 Request 对象。那这个核心过滤器组件就是SessionRepositoryFilter

SessionRepositoryFilter 见名知意负责session持久化的过滤器。

过滤器源码:

  • SessionRepositoryFilter 会率先拦截到这个请求。

  • 它并不会直接把原始的 HttpServletRequest 传给后面的 Controller。

它创建了两个包装类:

  • SessionRepositoryRequestWrapper : 继承自 HttpServletRequestWrapper
  • SessionRepositoryResponseWrapper : 继承自 HttpServletResponseWrapper

重点是它重写了包装类中的getSession() 方法

当你运行 request.getSession() 时,实际上运行的是包装类里的方法.

不再去 Tomcat 的内存里找 ,而是去 SessionRepository(对于你来说就是 RedisIndexedSessionRepository)里找

如上图 spring在构建SessionRepositoryFilter过滤器的时候,注入的就是RedisIndexedSessionRepository。专门负责session的增删改查。

SessionRepository是顶级抽象接口,RedisIndexedSessionRepository实现了该接口

包装类的getsession方法

上面是getsession包装后的源码,我把重要的代码标记出来了,我们一步一步分析。

首先是getCurrentSession

它的本次是请求内缓存,SessionRepositoryFilter 包装了 Request。在同一个 HTTP 请求的整个生命周期内(从进入 Filter 到 Controller 再到 Filter 出去),你可能会多次调用 request.getSession()

为了性能考虑,Spring Session 没必要每次调用 getSession() 都去查一遍 Redis。所以:

  • 第一次调用 getSession() :会去执行 getRequestedSession()(查 Redis/Cookie),查到后把结果存入 Request 的一个内部属性(Attribute)里。
  • 第二次及以后调用getCurrentSession() 就能直接从 Request 属性里把刚才那个对象拿出来。
ini 复制代码
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null) {
   return currentSession;
}
typescript 复制代码
private HttpSessionWrapper getCurrentSession() {
   return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR);
}

public Object getAttribute(String name) {
    return this.request.getAttribute(name);
}

也就是说请求第一次过来,调用request.getSession(),看看当前的 HttpServletRequest 对象里是不是已经有一个 HttpSessionWrapper 了,第一次肯定没有嘛,就会调用getRequestedSession(),真正去拿 Cookie (获取 SessionId),然后拿着 ID 去 Redis 找。 如果 Redis 找到了,就 new 一个包装类,并调用 setCurrentSession() 把它存到 Request 属性里

它就像是一个**"一级缓存"**:

  • 一级缓存getCurrentSession() ------ 存在当前请求对象里,生命周期仅限本次请求。
  • 二级缓存getRequestedSession() ------ 存在 Redis 里,生命周期是全局的。

这就是为什么你在同一个 Request 里反复调用 getSession(),拿到的永远是同一个内存对象的原因。

接下来我们看看getRequestedSession

使用了requestedSessionCached标记,防止每次调用request.getSession()都会触发Redis 的 HGETALL 查询。也就是说如果当前已经缓存了就直接使用缓存的,如果没有缓存就去redis中看有没有。,避免了多次网络IO.

setCurrentSession

如果从redis中查询到了当前的session,就调用setCurrentSession保存到当前请求的作用域内,这样就和第一步闭环了,第一步是先从request作用域去获取。二级缓存防止多次网络IO

createSession()

也就是说如果用户没带cookie或者Cookie 里的 ID 在 Redis 里查不到,就会createSession() 产生一个新的 ID。存在内存中,然后调用setCurrentSession() 把这个新 Session 塞进 Request 的属性,绑定到请求上。供后续使用。

相关推荐
晓杰在写后端15 分钟前
从0到1实现Balatro游戏后端(9):Blind奖励结算与金币系统实现
后端·游戏开发
Patrick_Wilson18 分钟前
幂等到底是什么?从前端视角讲透 SQL、HTTP 与 POST 接口的幂等设计
前端·后端·架构
凌览19 分钟前
一人公司别再上 Jenkins,真不值
前端·后端
菜鸟谢23 分钟前
Rust 元组与数组内存管理笔记
后端
oil欧哟24 分钟前
Codex 最佳实践(超级长文):先搞懂 AI,再用好 AI
前端·人工智能·后端
AskHarries24 分钟前
把一个外部系统接成 MCP 工具
后端·程序员
释然小师弟40 分钟前
Android开发十年:反思与回顾
android·后端·嵌入式
雪隐43 分钟前
个人电脑玩AI-04让5060 Ti给你打工——本地FLUX.2 Klein 的 AI 图片生成
人工智能·后端
掘金者阿豪1 小时前
多台服务器日志怎么统一清理?Ansible、Cron与cpolar自动化方案
后端
浮游本尊2 小时前
Java学习第45天 - 消息队列入门、异步解耦与最终一致性(RabbitMQ / RocketMQ)
后端