分布式之session共享问题 4种解决方案及spring session的使用

您所在的位置:网站首页 数据共享问题有哪些 分布式之session共享问题 4种解决方案及spring session的使用

分布式之session共享问题 4种解决方案及spring session的使用

2024-07-15 22:15| 来源: 网络整理| 查看: 265

session在分布式环境下存在的问题

由于HTTP协议是无状态的,在开发中我们可以将用户的信息存储在服务器的session中,并生成与之相对应的JSESSIONID通过cookie返回给浏览器。浏览器下次访问,cookie会自动携带上次请求存储的数据(JSESSIONID)到服务器中,服务器根据JSESSIONID找到对应的session,从而获取用户的信息。

该机制在单体应用中是没有问题的,但是如果在分布式环境下,会产生session共享问题,即session的数据在服务1中存在,但是在服务2中不存在。

就会出现下面的问题: 在这里插入图片描述 假设用户第一次访问的是会员服务1,会员服务1将用户的信息记录在自己的session中,但是当用户第二次访问的是会员服务2时,就会找不到用户信息

session共享解决方案 session复制

服务器将自己的session数据传送给其他服务器,使得每个服务器都拥有全量的数据。 在这里插入图片描述

优点:tomcat原生支持,只需要修改配置文件即可 缺点:

session同步需要数据传输,会占用大量带宽,降低服务器集群的业务处理能力任意一台web-server保存的都是所有web-server的session总和,浪费了大量的空间,且受内存限制无法水平扩展更多的web-server大型分布式集群情况下,由于所有web-server都要全量保存数据,所以此方案不可取。 客户端存储

用户的信息不再保存在服务器中,而是保存在客户端(浏览器)中。 在这里插入图片描述 优点:服务器不需要保存用户信息,节省服务器资源 缺点:

每次http请求,携带用户在cookie中的完整信息,浪费网络带宽用户信息存放在cookie中,cookie有长度4k限制,不能存放大量信息用户信息存储在cookie中,存在泄漏、篡改、窃取等安全隐患

一般情况下不会使用这种方案。

hash一致性

nginx负载均衡的时候采用ip-hash策略,这样同一个客户端每次的请求都会被同一个服务器处理。 在这里插入图片描述 优点:

只需要修改nginx配置,不需要修改应用程序代码可以支持web-server水平扩展

缺点:

session还是存在web-server中的,所以web-server重启可能导致部分session丢失,影响业务,如部分用户需要重新登录如果web-server水平扩展,rehash后session重新分布,也会有一部分用户路由不到正确session。

但以上缺点其实问题不大,因为session本来也是有有效期的,所以这个方案也经常被采用。

统一存储

jsessionid这个cookie默认是系统域名。当我们分拆服务,不同域名部署的时候,我们可以使用如下解决方案。

将用户的信息存储在第三方中间件上,做到统一存储,如redis中,所有的服务都到redis中获取用户信息,从而实现session共享。

优点

没有安全隐患可以水平扩展服务器重启或扩容都不会造成session的丢失

不足:

增加了一次网络调用,速度有所下降需要修改应用程序代码,如将所有的getSession方法替换为Redis查数据的方式。但这个问题可以通过spring session完美解决 整合spring session

现在我们知道,我们可以将session的信息存储在第三方数据库中,比如redis。但如果是我们自己去写这个逻辑的话太过麻烦。而spring session可以很简单的帮助我们实现这个功能。

官网地址:spring session官网地址 在这里插入图片描述 1、添加依赖

org.springframework.session spring-session-data-redis

2、配置文件添加配置

spring.session.store-type=redis

3、配置redis连接

spring.redis.host=localhost # Redis server host. spring.redis.password= # Login password of the redis server. spring.redis.port=6379 # Redis server port.

4、使用json序列化机制

@Configuration public class SessionConfig { @Bean public RedisSerializer springSessionDefaultRedisSerializer() { return new GenericFastJsonRedisSerializer(); } }

spring session默认是jdk序列化机制,要求类需要实现Serializable接口,序列化后是二进制,人看不懂。使用json序列化机制就没有这些问题。

5、在springboot启动类中添加@EnableRedisHttpSession注解

这样就OK了

扩展

session不能跨不同域名共享

当我们的认证微服务以及其他微服务使用的是俩个不同的域名时,即使使用了spring session也会存在不同域名的共享问题。

比如,认证服务的域名为auth.fcpmall.com,订单服务的域名为 order.fcpmall.com,这种情况下,即使在认证服务登录成功,将用户的信息保存在redis中,订单服务也无法查询到。

session不能跨不同域名共享的原因

先回顾一下正常的session流程:

session依赖于cookie的,服务器会将JSESSIONID放到cookie中,并返回给服务器。浏览器下次访问时,携带的cookie信息中含有JSESSIONID,所以服务器可以根据JSESSIONID找到对应的session

在不同域名下会发生什么?

首先你需要知道浏览器在发送http请求时,只会携带domain为当前域名以及父域名cookie信息。也就是从order.fcpmall.com发出的http请求只会携带domain为order.fcpmall.com和fcpmall.com的域名信息。浏览器在设置域名的时候默认使用的是当前的域名。即认证服务的JSESSIONID会被保存在domain为auth.fcpmall.com的cookie中综上,订单服务在发送请求的时候,没有携带含有JSESSIONID的cookie信息。所以找不到对应的session信息

在这里插入图片描述

知道了原因后,解决请来就很简单了,只需要在设置cookie的时候,指定domain为父域名fcpmall.com即可。

@Configuration public class SessionConfig { @Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer = new DefaultCookieSerializer(); serializer.setCookieName("JSESSIONID"); serializer.setCookiePath("/"); serializer.setDomainName("fcpmall.com"); return serializer; } }

这篇博客的知识点总结: 在这里插入图片描述 脑图链接地址

下面的部分由于我水平有限,写得不太好,所以选看即可

spring session核心原理

为什么spring session可以在不修改应用程序代码的前提下,将getSession方法替换为Redis查询数据的方式?

原理很简单,在我们添加@EnableRedisHttpSession注解的时候,它会为我们创建一个名为springSessionRepositoryFilter的bean,这个bean实现了Filter接口,在过滤器中将原先的HttpSession替换掉了,采用了装饰者模式。

下面进行初浅的源码分析(源码这一块虽然我现在还很弱,源码也很难读,但我认为这一块还是必要去锻炼的,所以慢慢来吧)

//在EnableRedisHttpSession中导入RedisHttpSessionConfiguration配置类 @Import({RedisHttpSessionConfiguration.class}) @Configuration( proxyBeanMethods = false ) public @interface EnableRedisHttpSession{ ... } @Configuration( proxyBeanMethods = false ) //RedisHttpSessionConfiguration继承SpringHttpSessionConfiguration public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware { //注入sessionRepository,用来对redis进行增删操作的类 @Bean public RedisIndexedSessionRepository sessionRepository() { 。。。 } ... } //看看SpringHttpSessionConfiguration做了些什么 @Configuration( proxyBeanMethods = false ) public class SpringHttpSessionConfiguration implements ApplicationContextAware { //在容器中注入SessionRepositoryFilter,该类继承了Filter(关键) @Bean public SessionRepositoryFilter springSessionRepositoryFilter(SessionRepository sessionRepository) { SessionRepositoryFilter sessionRepositoryFilter = new SessionRepositoryFilter(sessionRepository); sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver); return sessionRepositoryFilter; } ... } //这个过滤器中实现了狸猫换太子 @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); SessionRepositoryFilter.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryFilter.SessionRepositoryResponseWrapper(wrappedRequest, response); try { //注意了,传入下一个过滤器的request和response已经被换成了wrappedRequest,wrappedResponse。这里使用了装饰者模式 filterChain.doFilter(wrappedRequest, wrappedResponse); } finally { wrappedRequest.commitSession(); } } }

到这里知道了,当我们使用spring session的时候,在经过spring session过滤器的时候HttpServletRequest已经被换成了SessionRepositoryResponseWrapper,接下来我们就看一下这个类对getSession动了什么手脚。

private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { public SessionRepositoryFilter.SessionRepositoryRequestWrapper.HttpSessionWrapper getSession(boolean create) { SessionRepositoryFilter.SessionRepositoryRequestWrapper.HttpSessionWrapper currentSession = this.getCurrentSession(); if (currentSession != null) { return currentSession; } else { //获取session S requestedSession = this.getRequestedSession(); ..... } private S getRequestedSession() { if (!this.requestedSessionCached) { List sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver.resolveSessionIds(this); Iterator var2 = sessionIds.iterator(); while(var2.hasNext()) { String sessionId = (String)var2.next(); if (this.requestedSessionId == null) { this.requestedSessionId = sessionId; } //从sessionRepository获取session S session = SessionRepositoryFilter.this.sessionRepository.findById(sessionId); if (session != null) { this.requestedSession = session; this.requestedSessionId = sessionId; break; } } this.requestedSessionCached = true; } return this.requestedSession; } }

还记得前面RedisHttpSessionConfiguration配置的RedisIndexedSessionRepository吗?被spring session狸猫换太子后,我们后面对HttpSession的操作其实都是由这个类完成的。也就是说对session的增删操作实际上已经换成了对redis的增删操作了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3