登录模块-分布式会话设计文档

预备知识

会话保持

我们都知道HTTP是无状态协议,如果我们在同一网站进行连续两次请求时,服务器是无法识别两次请求的相关性,尤其是需要登录认证的网站,如果第一个请求是登录请求,服务器认证成功,然后进行第二次请求,服务端仍然无法识别该请求的具体身份,就会返回“403 Forbidden”或者重定向至登录页面,然后需要进行发送登录请求进行重定向至该页面,这无疑是极差的用户体验。所以我们需要存在一个服务器能识别的凭据,来表明自己的认证身份,一般存在两种方式:
Cookie:一般存储在客户端,每次请求时需要携带Cookie,优点:Cookie保存在客户端,缓解服务端会话存储压力;缺点:Cookie可能会很臃肿,降低请求效率,而且其中可能存储了一些敏感信息,安全度不高。
Session:一般存储在服务端,客户端需要在首部Cookie或者查询参数中携带Session id;优点:服务端保存session信息,只对外暴露Session id,能较好保护信息;缺点:服务端需要保存较多的会话信息。

分布式会话解决方案

Cookie不存在集群session共享的问题,因为Cookie中已经保存足够的能识别用户身份的信息,每台主机的应用服务都能解析相应Cookie并获取到其中的用户信息。而对于服务器保存的Session信息,服务器的会话信息都是在本地存储的,但是我们对集群进行负载均衡时就会出现问题,如果登录请求和后续请求不是转发到同一台服务器进行处理的话,就会出现会“403 Forbbiden”的情况。为了保证应用服务的可伸缩性以及服务可用性,能支持平滑的横向扩展,需要保证能在集群各个主机间进行共享。

集中式会话
把 Session 持久化到数据库

这种方式的工作流程如下:每次收到请求时获取session,然后刷新session访问时间,完成请求后,需要同步至数据库,这对数据库的读写性能要求相当高,而且需要添加定时任务对数据库的过期的会话信息进行及时的清理。
就我看来,该方案挺不现实的,一是会话是读写频率都特别高的数据,这使得数据库的压力相当巨大,比如锁竞争、日志输出、索引建立等,虽然可以使用缓存进行缓解,但缓存与数据库同步本身也是一个比较复杂而且困难的问题;二是数据库一般存储持久化数据,而对于会话数据,一般会话周期比较短,需要频繁删除,这使得索引树的节点频繁分裂和合并,这相当损失性能,这也是相当不切实际的。

使用存储中间件

内存数据库通常被用作缓存数据信息,同样我们可以使用它来存储会话信息,读写效率都特别高,这种中间件比如Redis、Memcache等。

Session复制

这是通过Web容器之间进行session信息的复制同步,每台服务器都具有全部的Session信息,这种方式需要占用大量的带宽资源,不适合在大型集群中使用。

Session绑定

也称为粘性会话(Sticky Session),这个本不属于Session共享部分,但同样能解决集群中session的访问问题,这就是负载均衡策略中的ip_hash,负载均衡设备会将来自同一个ip的请求都转发至同一个应用服务器中,而Session只需要存储在该服务器即可。

Spring-security集中式会话的设计

Spring-security应用需要区分Spring-security会话和Tomcat会话,其中Spring-security会话和Tomcat会话具有相同的会话id,当客户端向服务端进行请求时,都会生成一个Tomcat会话,只有当客户端进行登录认证后,Tomcat会话才会携同凭证一起注册到Spring-security的会话中心中。两种会话具有相同的生命周期,Spring-security的会话注册中心会监听Tomcat会话销毁事件,当其接收到会话DestroyedEvent事件时,会移除相关的会话信息。

在通过Spring-security认证之后的Session,先会进行Session Fixation然后后将其会话信息跟凭证信息一起存入redis,下面是Redis的相关存储结构:

在这里插入图片描述
其中Principal是指用户的凭证信息,一般在系统中体现为用户ID或者用户名,我们会在Redis中存储相关用户名以及一些不可变的信息。目前并未设置账号的同时登录设备数限制,用户会话需要进行Set存储,以兼容多台设备同时登录的情况,保证所有登录会话ID皆可用。会话信息主要存储用户名以及上次访问时间,最近访问时间可以用于支持会话过期策略。
下图是对RedisSessionRegistry的会话注册中心的简易设计,具体实现主要就是通过实现SessionRegistry接口进行实现:

在这里插入图片描述

我们考虑这样一种情况,一台服务器出现了一些问题,我们需要重启机器并重新部署服务,Web容器关闭时会对会话信息进行销毁,这就导致服务器可能会误删一些其他服务器仍然活跃的会话,对于这种情况,我们需要在Redis中存储持有该会话的主机列表,只有当列表中只有本机(即只有一个注册主机)时,才能进行会话移除,下图为Redis存储结构:
在这里插入图片描述

为了保证会话ID在每台服务器的可用性,我们需要扩展Tomcat的SessionManager,使其将session查找范围从本机,延伸到Redis注册中心,保证各台机器已认证的会话ID都能被其他机器识别使用。由于Tomcat在会话过期后会自动移除会话,移除会话的策略是通过判断上次访问时间距离现在时间是否已经超过会话的最大生存时间,所以我们请求某台主机时,需要让其他主机也同时接收到该事件,然后进行会话访问更新。通过Redis PUB/SUB将一个主机的访问事件发布到其他机器,其他主机接收到该事件后进行刷新session的操作,保证会话生命周期的同步性。

在这里插入图片描述

这个设计目前并非最优,但是基本可以满足需要,我们还可以考虑思考[1]部分进行改良,但这个改良只是降低了系统的复杂性,由于存储结构的改变和和网络通信延迟的增加,能否保持较高的访问性能还有待商榷。


一些思考:
1、是否可以通过对StandardSession进行扩展,将会话上次请求时间直接从Redis中获取,以及一些参数也可以之间存储到Redis中去,这样可以省去Redis PUB/SUB进行会话同步的部分。
2、对于SessionRegistry,由于我们不需要使用到密码等其他信息…
3、使用memcache是否会具有比Redis更高的性能:msm解决方案

参考来源:
1、https://www.cnblogs.com/longren/p/11018850.html
2、https://my.oschina.net/u/1167421/blog/606861?_from=gitee_rec
3、https://www.cnblogs.com/moonandstar08/p/5582281.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子的木木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值