服务提供者之间的会话共享关系
一套分布式微服务集群可能会运行几个或者几十个网关(gateway),以及几十个甚至几百个Provider微服务提供者。如果集群的节点规模较小,那么在会话共享关系上,同一个用户在所有的网关和微服务提供者之间共享同一个分布式Session是可行的,如图6-8所示。
图6-8 共享分布式Session
如果集群的节点规模较大,分布式Session在IO上就会存在性能瓶颈。除此之外,还存在一个架构设计上的问题:在网关(如Zuul)和微服务提供者之间传递Session ID,并且双方依赖了相同的会话信息(如用户详细信息),将导致网关和微服务提供者、微服务提供者与微服务提供者之间的耦合度很高,这在一定程度上降低了微服务的移植性和复用性,违背了系统架构高内聚、低耦合的原则。
架构的调整方案是:缩小分布式Session的共享规模,网关(如Zuul)和微服务提供者之间按需共享分布式Session。网关和微服务提供者不再直接传递Session ID作为用户身份标识,而是改成传递用户ID,如图6-9所示。
图6-9 Session共享的架构与实现方案
以上介绍的Session共享的架构,第一种可理解为全局共享,第二种可理解为局部按需共享。无论如何,Session共享的架构与实现方案肯定不止以上两种,而且以上第二种方案也不一定是最优的。疯狂创客圈的crazy-springcloud脚手架对上面的第二种分布式Session架构方案提供了实现代码,供大家参考和学习。
分布式Session的起源和实现方案
HTTP本身是一种无状态的协议,这就意味着每一次请求都需要进行用户的身份信息查询,并且需要用户提供用户名和密码来进行用户认证。为什么呢?服务端并不知道是哪个用户发出的请求。所以,为了能识别是哪个用户发出的请求,需要在服务端存储一份用户身份信息,并且在登录成功后将用户身份信息的标识传递给客户端,客户端保存好用户身份标识,在下次请求时带上该身份标识。然后,在服务端维护一个用户的会话,用户的身份信息保存在会话中。通常,对于传统的单体架构服务器,会话都是保存在内存中的,而随着认证用户增多,服务端的开销会明显增大。
大家都知道,单体架构模式最大的问题是没有分布式架构,无法支持横向扩展。在分布式微服务架构下,需要在服务节点之间进行会话的共享。解决方案是使用一个统一的Session数据库来保存会话数据并实现共享。当然,这种Session数据库一定不能是重量级的关系型数据库,而应该是轻量级的基于内存的高速数据库(如Redis)。
在生产场景中,可以使用成熟稳定的Spring Session开源组件作为分布式Session的解决方案,不过Spring Session开源组件比较重,在简单的Session共享场景中可以自己实现一套相对简单的RedisSession组件,具体的实现方案可以参考疯狂创客圈的社群博客“RedisSession自定义”一文。从学习角度来说,自制一套RedisSession方案可以帮助大家深入了解Web请求的处理流程,使得大家更容易学习Spring Session的核心原理。
Spring Session作为独立的组件将Session从Web容器中剥离,存储在独立的数据库中,目前支持多种形式的数据库:内存数据库(如Redis)、关系型数据库(如MySQL)、文档型数据库(如MogonDB)等。通过合理的配置,当请求进入Web容器时,Web容器将Session的管理责任委托给Spring Session,由Spring Session负责从数据库中存取Session,若其存在,则返回,若其不存在,则新建并持久化至数据库中。
Spring Session的核心组件和存储细节
这里先介绍Spring Session的3个核心组件:Session接口、RedisSession会话类、SessionRepository存储接口。
1.Session接口
Spring Session单独抽象出Session接口,该接口是SpringSession对会话的抽象,主要是为了鉴定用户,为HTTP请求和响应提供上下文容器。Session接口的主要方法如下:
(1)getId:获取Session ID。
(2)setAttribute:设置会话属性。
(3)getAttribte:获取会话属性。
(4)setLastAccessedTime:设置会话过程中最近的访问时间。
(5)getLastAccessedTime:获取最近的访问时间。
(6)
setMaxInactiveIntervalInSeconds:设置会话的最大闲