集群/分布式架构基于Session的登录设计
Session是进行会话控制、用户追踪的重要手段。在单体架构中使用Session十分简便快捷,是登录权限控制的重要手段。但是,在集群架构、分布式架构中却会面临Session不一致的问题,可以通过Session 同步、Session共享的方式来解决这个问题。
4.8.1 Session的工作原理
客户端的接口请求大体分为两类,一类需要用户登录后才可以访问,另一类不需要登录即可访问。对于第一类接口请求就需要服务端
具有会话保持的能力,而Session就是一种常用的会话保持技术,
Session会话保持原理如图4-29所示。
(1) 客户端登录,提交用户名和密码。
(2) 服务端保存生成Session,并且将Session存储在内存中。
(3) 服务端将session_id返回给客户端。
(4) 客户端自动将session_id存储到Cookie中。
(5) 客户端的所有后续请求均会携带Cookie。
(6) 后端通过Cookie中的session_id获取Session信息,进而校验用户状态是否依然保持,并延长用户状态保持的有效期。
图4-29 Session会话保持原理
传统的Session方式的优点是实现简单,开发成本低,然而缺点也比较突出,可以总结为以下6点。
(1) 基于服务器内存,难以水平扩展和集群化。
(2) 高并发、用户使用量大时会导致Session大量占用内存,降低服务器性能。
(3) Cookie可能被禁用或被用户删除。
(4) Cookie安全性不够高,可以被截取和篡改。
(5) Cookie存储空间很小,长度和数量都有限制。
(6) Web浏览器的Cookie可以自动管理,原生端要自己实现
Cookie管理。
Session模式比较适用于单体架构开发,用户量小、访问量小的内部系统使用,实现简单。下面将重点针对集群架构/分布式架构下的
Session进行讲解。
如果读者对Cookie不了解,则可以先阅读以下基础知识内容。
1. Cookie的概念
Cookie是一段不超过4kB的小型文本数据(不同浏览器有所区别,一般为4095~4097B),保存在浏览器中,经常用于存储用户相关信息、Token、SessionID等。不同浏览器对于每个域能存储的Cookie数量限制也不相同。例如,IE6浏览器每个域能存储30个Cookie,Chrome浏览器每个域能存储53个Cookie,而Safari浏览器则没有数量限制。
2. Cookie的组成和格式
如上所示,一个Cookie由六部分组成:名称、值、域、失效时间、路径和安全标志。
(1) 名称(name):一个唯一确定的Cookie的名称,不区分大小写。
(2) 值(value):数据字符串,最好加密存储。
(3) 域(domain):Cookie对于哪个域是有效的,如果没有设置,则默认为设置Cookie的那个域。
(4) 失效时间(expires):设定Cookie在什么时间失效,失效后即被删除。expires代表具体失效时间,Max-Age代表在多少秒之后
Cookie失效,Max-Age的优先级大于expires。当Max-Age=-1时,代表永不失效。
(5) 路径(path):代表访问哪些域的路径会自动携带Cookie数据发送给服务端,“/”表示没有限制。
(6) 安全标志(secure):设置之后只有在使用SSL连接
(HTTPS)时才会自动携带Cookie数据发送给服务端。
3.如果存储的Cookie数量已经达到上限,继续存储会怎样?
Safari浏览器没有Cookie数量限制,因此不存在这个问题。
IE和Opera浏览器使用LRU(Least Recently Use,最近最少使用)
算法,当Cookie达到上限后,自动删除最旧、使用最少的Cookie,从而给新Cookie留出空间。
Firefox浏览器保留最后设置的Cookie数据,随机删除Cookie数据,官方并没有明确说明删除算法。
4.8.2 集群/分布式架构下的Session设计
在集群架构和分布式架构下使用Session会引发哪些问题,这些问题是如何产生的,怎样才能够解决这些问题呢?
1.在集群负载架构下Session会引发什么问题?
如图4-30所示,随着订单服务压力的上升,决定将原来的1个节点扩充为2个节点,以集群的方式对外提供服务,使用Nginx做反向代理
(Nginx默认使用轮询策略)。
(1) 用户登录时访问了订单服务A,登录成功后Session信息保存在A服务器的内存中。
(2) 当用户查询自己的订单信息时,通过Nginx的轮询策略,请求转发给了订单服务B,但是B服务器中并没有存储用户的Session信息,所以认为用户还没有登录,直接跳转到登录页面。
图4-30 集群模式下的Session不同步
2.在分布式架构下是否也具有相同的问题?
如图4-31所示,在分布式架构下,用户登录时访问了用户微服务,登录成功后Session信息保存在用户微服务的内存中。当用户查询自己的订单信息时,会请求订单微服务,但是此服务的内存中并没有存储
用户的Session信息,所以认为用户还没有登录,直接跳转到登录页面。可见,无论是在集群负载构架还是在分布式架构下,都面临着相同的问题。
图4-31 分布式模式下的Session不同步
这就是典型的Session迁移和Session共享问题,如果需要进行会话保持,就要保证每个服务器都能够获取用户的Session信息。解决方案主要有4种:Session同步、负载控制、共享存储和抽象
Session服务。
(1) Session同步。
如图4-32所示,在集群架构下,用户登录成功后,订单服务A存储 Session,并将Session同步给订单服务B和C,因此任意服务器上产生新的Session数据,都要通知给其他全部节点。如果有N个服务器,就要通知给N-1个节点。当服务节点非常多时,用户登录请求一旦增加,就会引发消息风暴,系统性能急剧下降,并且必须考虑Session同步过程中可能发生的各种网络问题。
因此,这种方案只适用于用户数量较少,2~3台的小型集群架构使用,多于3台时出现问题的概率就会急剧上升。
图4-32 集群模式下的Session同步
(2) 负载控制。
集群架构只需要修改Nginx的分发策略,让同一个客户端的请求全部转发到同一个服务器即可,主要使用哈希策略,如IP哈希策略或其他基于用户标识的哈希策略。
IP哈希策略的目的是将相同IP来源的用户请求始终只转发到同一台服务器上。例如,用户首次访问系统时请求转发给了服务A,那么后续所有请求,只要客户端IP不变则全部转发到服务A中。这样就不需要进行Session同步,也可以与客户端保持会话。
但是,这种方案会导致比较严重的流量倾斜问题,可能存在大量的请求发往服务A,而只有少量请求发往服务B,从而导致服务A的性能急剧下降,而服务B却十分空闲。
如图4-33所示,来源于客户端10.1.2.3的请求,全部被转发至订单服务A中。
图4-33 集群模式下Session同步方案
另外,这种方案只适用于集群架构,而不适用于分布式架构。如果修改了网关的转发策略,让相同IP的请求始终转发给同一个微服务,由于每个服务端节点不具备全部的功能,从而无法达到要求。如图4-34所示,用户第一次查询订单的请求被转发到用户微服务,第二次查询订单的请求也被转发到用户微服务,而用户微服务并不具备查询订单的能力,因此无法提供服务。
图4-34 分布式模式下的Session共享问题
(3) 共享存储。
在集群架构下,用户登录时服务端可以将Session信息不存储到本机内存中,而是存储到关系型数据库(MySQL、Oracle等)或NoSQL 数据库(Redis、Memcached等)中。当用户进行业务访问时,各个服务端节点就可以从这些共享存储设备中获取Session信息,从而完成会话保持,如图4-35所示。
图4-35 集群模式下的Session共享策略
在分布式架构下,用户登录时访问用户微服务,并将Session信息存储到共享设备中,当访问其他微服务时,就可以从共享设备中读取,也可以完成会话保持,如图4-36所示。
图4-36 分布式模式下的Session共享策略
(4) 抽象Session服务。
可以将Session共享存储设备替换为Session服务,由此服务统一提供Session的创建、保存、查询、对比服务。使用Session服务的好处是可以开发很多附加功能,如Session的可视化管理、Session的监控统计等。同时,支持单体架构、集群架构、微服务架构,如图4-37所示。
图4-37 抽象Session共享服务策略
方案选型使用原则:对于小型系统,推荐采用方案(1)和
(2);对于大型系统,推荐采用方案(3)和(4)。