首页 > 编程知识 正文

spring分布式事务实现,spring分布式框架

时间:2023-05-03 08:33:18 阅读:32048 作者:3810

session在分布式环境中存在的问题是,HTTP协议是无状态的,因此在开发过程中可以将用户的信息存储在服务器的session中,生成相应的JSESSIONID并在cookie中返回给浏览器。 下次浏览器访问时,cookie会自动将上次请求保存的数据(JSESSIONID )携带到服务器上,服务器根据JSESSIONID找到相应的session以获取用户的信息。

该机制在单个APP应用中没有问题,但在分布式环境中会出现session共享问题,即服务1中存在session数据,而服务2中不存在session数据。

将出现以下问题:

假设用户第一次访问会员服务1。 会员服务1将用户的信息记录到自己的session中,但在用户第二次访问会员服务2时,找不到用户信息

session共享解决方案sessionreplicationserver将自己的session数据传输到其他服务器,使每个服务器都拥有所有数据。

好处: tomcat本机支持,只需更改配置文件

缺点:

会话同步需要数据传输,并占用大量带宽。 会降低服务器集群业务处理能力的web-server会保存所有web-server的会话合计,浪费大量空间。 另外,对于受内存限制无法水平扩展更多web-server的大型分布式集群,由于所有web-server都必须完全保存数据,因此客户端保存的用户信息不是服务器,而是客户端(浏览器

优点:服务器不需要存储用户信息,节约服务器资源

缺点:

每次http请求,cookie都会携带用户的完整信息,而浪费网络带宽的用户信息存储在cookie中。 cookie长度限制为4k,无法存储大量信息的用户信息存储在cookie中,存在泄露、篡改、被盗等安全隐患一般情况下不会使用这种方案。

在hashconsistencynginx负载平衡期间采用ip-hash策略,以便每次在同一服务器上处理来自同一客户端的请求。

好处:

只需更改nginx配置,就可以在不更改APP应用程序代码的情况下解决web-server水平扩展的缺点。

由于会话仍存在于web-server上,因此web-server重新启动可能会导致某些会话丢失,从而影响业务。 例如,如果某些用户需要重新登录,web-server将水平扩展,并且在刷新后重新分发会话,一些用户将无法路由到正确的会话。 但是,上述缺点其实不是什么问题。 因为session本来就有有效期,所以这个方案也被经常采用。

默认情况下,名为统一存储jsessionid的cookie是系统域名。 如果要拆分服务并使用不同的域名进行部署,可以使用以下解决方案:

将用户信息存储在第三方中间件中以实现统一存储。 例如,在redis中,所有服务都将用户信息检索到redis中以实现session共享。

好处

如果没有安全隐患,水平扩展服务器重新启动和扩展并不足以解决会话丢失问题。

如果添加了网络调用并降低了速度,则必须更改APP应用程序代码,例如用Redis检查数据的方法替换所有getSession方法。 但是,我们知道spring session可以完美解决这个问题,集成spring session现在可以将session信息存储在第三方数据库(如redis )中。 但是,我们自己写这个逻辑很麻烦。 spring session有助于实现此功能。

官方网站地址: spring session官方网站地址

1、添加依赖项

ependencygroupidorg.spring framework.session/groupidartifactidspring-session-data-redis/artifact id/dependency 2

spring.session.store-type=redis 3,配置redis连接

spring.redis.host=localhost # redis server host.spring.redis.password=# loginpasswordoftheredissserver.spring .

@ configurationpublicclasssessionconfig { @ beanpublicredisserializerobjectspringsessiondefaultredisserializer { return new } } spring session缺省为jdk序列化机制,类需要实现序列化接口,序列化后是二进制,人不知道。 使用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即可。

@Configurationpublic 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继承SpringHttpSessionConfigurationpublic 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 <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(SessionRepository<S> sessionRepository) { SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter(sessionRepository); sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver); return sessionRepositoryFilter; } ...}//这个过滤器中实现了狸猫换太子@Order(-2147483598)public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository); SessionRepositoryFilter<S>.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<S>.SessionRepositoryRequestWrapper.HttpSessionWrapper getSession(boolean create) { SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper.HttpSessionWrapper currentSession = this.getCurrentSession(); if (currentSession != null) { return currentSession; } else { //获取session S requestedSession = this.getRequestedSession(); ..... } private S getRequestedSession() { if (!this.requestedSessionCached) { List<String> 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的增删操作了。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。