理解分布式Session处理来看看spring怎么做的
创始人
2024-03-31 19:23:43
0

Spring Session使用Redis存储Session原理理解

1、背景

HttpSession

​ Session 是我们在做java web项目 或者是其他的web项目时 一定会接触的,在学习中,常常被我们用来存储用户的一些关键信息,如:登录状态等

​ 但是这仅限于单体应用 一旦变成了集群部署,session处理起来 还是比较的麻烦的,要么是保证不了安全性,要么是保证不了性能,很是难受,spring家族是出了名的贴心,所有我们在他的全家桶中也可以找到有关session的框架,

​ 博主最近学习微服务项目的时候,接触到这个框架 感觉相当的实用,于是打算给大家分享一下这个好用的框架,并且分享一下学到的原理思路

简介

Spring Session是Spring的项目之一,GitHub地址:https://github.com/spring-projects/spring-session。Spring Session把servlet容器实现的httpSession替换为spring-session,专注于解决session管理问题。Spring Session提供了集群Session(Clustered Sessions)功能,默认采用外置的数据源来存储Session数据,以此来解决Session共享的问题。

2、场景理解

​ 从场景来理解这个session工具的好处,这里我们以微服务两个域名(父子域名)来举例

auth.mall.com

mall.com

​ 我们注册需要转到 auth.mall.com中调用用户服务来完成登录,登录完成我们需要记录用户的登录状态

单体服务我们直接httpsession来存放用户的状态,但是我们如果还这么做的话,就会出现这种状态

image-20221027142417543

​ 这里是session的作用域是在auth开头的子域名下,但是登录成功转到我们门户主页 mall.com的时候就会出现读取不到的问题。

​ 这里有几种解决方案,但是都有缺点,列表如下:

  1. nginx 负载均衡 IP哈希 每一次请求都到同一个服务器,但是随着数量的增多可能会出现问题
  2. tomcat session复制 这种的话 十分的占用资源且效率低下
  3. 就是用第三方数据源来存储 这种方式在分布式的环境下应用特别的多

这里我们就来介绍第三种

使用Redis来作为第三方的数据源来存储session的数据,但是我们使用的原生的数据要考虑很多东西比如过期,序列化,session的数据存储更新等等东西,这个时候我们就十分的期待能有一个封装好的适配第三方数据源的工具出现:Spring-Seesion,他完全适配spring的生态环境,可以即插即用,只需要简单的配置就行

依赖

      org.springframework.sessionspring-session-data-redis

配置

yaml只需要配置redis就行:

  redis:password: adminhost: xxxxxx

配置类:

@Configuration
public class SessionConfig {//设置cookie的作用于与名称@Beanpublic CookieSerializer cookieSerializer() {DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();//放大作用域cookieSerializer.setDomainName("gulimall.com");cookieSerializer.setCookieName("GULISESSION");return cookieSerializer;}//设置redis的序列化 这里使用的是 jackson@Beanpublic RedisSerializer springSessionDefaultRedisSerializer() {return new GenericJackson2JsonRedisSerializer();}} 

注解:

给配置类加上启动注解 @EnableRedisHttpSession

3、使用

​ 只需要在方法的参数上 加入 HTTPSession 就可以了,加入了启动注解 他会去封装原生的http的请求响应,接下里我们使用的session其实是被封装后的session

@GetMapping("/oauth/gitee/Success")
public String gitee(@RequestParam("code") String code, HttpSession session){session.setAttribute(AuthSeverConstant.LOGIN_USER, data);
}

​ 只需要在其他的服务中加入依赖配置完成之后,加上注解,就可以共享redis的session域

image-20221027151825365

他还做了很多的事情 比如 我们还在使用的时候 session的过期时间会自动的续上等操作,

4、核心原理

Spring-session 在我们使用session过程中是如何封装的呢?

先从我们使用的注解下手, EnableRedisHttpSession 导入了RedisHttpSessionConfiguration配置

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({RedisHttpSessionConfiguration.class})
@Configuration
public @interface EnableRedisHttpSession {int maxInactiveIntervalInSeconds() default 1800;String redisNamespace() default "spring:session";RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;String cleanupCron() default "0 * * * * *";
}

RedisHttpSessionConfiguration 向容器中注册了Redis适配的组件 RedisOperationsSessionRepository

用来操作redis中session的封装方法类

public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware, SchedulingConfigurer {@Beanpublic RedisOperationsSessionRepository sessionRepository() {RedisTemplate redisTemplate = this.createRedisTemplate();RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(redisTemplate);sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);if (this.defaultRedisSerializer != null) {sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);}sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);if (StringUtils.hasText(this.redisNamespace)) {sessionRepository.setRedisKeyNamespace(this.redisNamespace);}sessionRepository.setRedisFlushMode(this.redisFlushMode);int database = this.resolveDatabase();sessionRepository.setDatabase(database);return sessionRepository;}
}

接着我们去看看他继承的类 SpringHttpSessionConfiguration 这里应该是放了一些基础的封装配置,

RedisHttpSessionConfiguration对redis数据源 实现了针对性封装

@Configuration
public class SpringHttpSessionConfiguration implements ApplicationContextAware {private final Log logger = LogFactory.getLog(this.getClass());private CookieHttpSessionIdResolver defaultHttpSessionIdResolver = new CookieHttpSessionIdResolver();private boolean usesSpringSessionRememberMeServices;private ServletContext servletContext;private CookieSerializer cookieSerializer;private HttpSessionIdResolver httpSessionIdResolver;private List httpSessionListeners;public SpringHttpSessionConfiguration() {this.httpSessionIdResolver = this.defaultHttpSessionIdResolver;this.httpSessionListeners = new ArrayList();}@PostConstructpublic void init() {CookieSerializer cookieSerializer = this.cookieSerializer != null ? this.cookieSerializer : this.createDefaultCookieSerializer();this.defaultHttpSessionIdResolver.setCookieSerializer(cookieSerializer);}@Beanpublic  SessionRepositoryFilter springSessionRepositoryFilter(SessionRepository sessionRepository) {SessionRepositoryFilter sessionRepositoryFilter = new SessionRepositoryFilter(sessionRepository);sessionRepositoryFilter.setServletContext(this.servletContext);sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);return sessionRepositoryFilter;}
}

SpringHttpSessionConfiguration 里的init的方法初始化配置,看之前的配置类中设置的cookie设置载入到默认的session适配器中,另外的一个核心方法就是向容器放入了一个session数据操作过滤器,进入这个过滤器

@Order(-2147483598)
public class SessionRepositoryFilter extends OncePerRequestFilter {protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);SessionRepositoryFilter.SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryFilter.SessionRepositoryRequestWrapper(request, response, this.servletContext);SessionRepositoryFilter.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryFilter.SessionRepositoryResponseWrapper(wrappedRequest, response);try {filterChain.doFilter(wrappedRequest, wrappedResponse);} finally {wrappedRequest.commitSession();}}
}

找到这个doFilterInternal方法 很清晰就可以看到

这两个方法封装了我们的http原生的请求和响应,因为我们如果想设置session 需要去从httprequst里获取session

他就是利用了我们这一点,在获取之前对session进行封装,采用装饰者模式,之后我们getsession获取的就不是原生的session了 是spring封装之后的session

        SessionRepositoryFilter.SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryFilter.SessionRepositoryRequestWrapper(request, response, this.servletContext);SessionRepositoryFilter.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryFilter.SessionRepositoryResponseWrapper(wrappedRequest, response);

源码篇幅很长这里简单给大家用文字来介绍一下,以请求为例子

经过装饰之后 我们的getsession获取到的就是这个方法的返回值,他继承了HttpSessionWrapper,

这里判断为空的时候就去:S requestedSession = getRequestedSession();

	@Overridepublic HttpSessionWrapper getSession(boolean create) {HttpSessionWrapper currentSession = getCurrentSession();if (currentSession != null) {return currentSession;}S requestedSession = getRequestedSession();if (requestedSession != null) {if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {requestedSession.setLastAccessedTime(Instant.now());this.requestedSessionIdValid = true;currentSession = new HttpSessionWrapper(requestedSession, getServletContext());currentSession.setNew(false);setCurrentSession(currentSession);return currentSession;}}

getRequestedSession这个方法会用sessionRepository来查找session,而sessionRepository在我们之前的配置中被RedisOperationsSessionRepository并且注入到了容器中,所以可以使用redis来实现session的存储,让多服务可以共享session

	private S getRequestedSession() {if (!this.requestedSessionCached) {List sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver.resolveSessionIds(this);for (String sessionId : sessionIds) {if (this.requestedSessionId == null) {this.requestedSessionId = sessionId;}S session = SessionRepositoryFilter.this.sessionRepository.findById(sessionId);if (session != null) {this.requestedSession = session;this.requestedSessionId = sessionId;break;}}this.requestedSessionCached = true;}return this.requestedSession;}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...