场景描述
“查询推荐好友”是一个典型的社交服务场景,通常的需求是:用户未登录情况下,系统返回推荐好友列表:ABCDE,在用户登录态情况下,需要将用户已经关注过的人从系统默认给出的推荐好友列表中去掉,比如如果登录的用户U已经关注了用户A,那么U查询得到的推荐好友列表就应当是:BCDEF,A不会返回。
业务逻辑非常清晰,在实际服务设计过程中,会发现这个case隐含了匿名访问和登录状态访问两个case,在不同的架构风格下,应当如何去设计呢?可能遇到的问题是什么呢?接下来逐一探讨。
单体架构服务实现
我们先来看看这个case在单体服务下应当如何去实现。
没什么好考虑的,所有逻辑交给业务逻辑层完成,包括“判定用户是否有登录态”这一逻辑。通常这里的登录态使用web容器自带的session管理功能完成。用户登录之后,会在web容器中产生一个Session对象,并将sessionId写回到浏览器cookie中,客户端下次请求时戴上sessionId cookie,web容器读取cookie中的sessionId找到对应用户的Session对象,取出用户信息,如userId等。
大家都知道单体服务架构下,服务与会话强耦合,服务缺乏扩展性,于是我们尝试在分布式架构中实现这个case。
在分布式架构中的实现
通常分布式服务架构要求服务是无会话状态的,这样有利于服务水平扩展,提升服务的可扩展新和伸缩性。于是,我们将会话/登录态管理交给独立组件——会话服务器(sessionServer)实现,之前单体服务自行管理的会话交由sessionServer管理。
这里的SessionServer将多个领域/业务渠道的会话管理集中起来,职责单一且专一。同时,可以通过它实现SSO(单点登录)的需求。
不好的地方在于:所有的ServiceServer组件都要依赖于SessionServer组件完成登录态校验,SessionServer存在单点瓶颈/故障的隐患;
微服务架构实现
严格意义上来讲,下面讨论的设计只是和微服务架构常提到的api gateway沾边,但为了表述方便,我们仍然将下面的方案讨论定位为微服务架构实现。
微服务架构下,我们引入了gateway,并将“校验登录态权限”的职责交给gateway,可以避免分布式架构实现中,每个service组件都依赖SessionServer,且我们可以在gateway层对用户登录态做缓存,从而减少到SessionServer的链接和请求数。
大家可以看到上图中标蓝的部分存疑:gateway对于登录态校验结果应当如何处理呢?我们继续探讨下:
- 校验登录态不通过,gateway直接拒绝,不再透传请求到ServiceServer;
- 校验登录态不通过,gateway不直接拒绝,将请求透传给ServiceServer,由ServiceServer根据具体业务场景做相应处理,比如:强登录态校验的场景,直接报错;非强制校验登录态的场景,只需要尝试从登录态中获取用户信息即可,如“查询推荐好友”场景。
方案1简单粗暴,和现实中大厦的门禁一样,门卡有效则放行,没有门卡或者门卡失效则直接拒绝访问。那么“查询推荐好友”就需要拆分成两个case:有登录态场景和无登录态场景,由客户端先调用“有登录态场景查询推荐好友”,如果失败,则再调用“无登录态场景查询推荐好友”。客户端同学肯定会把服务端同学干掉。
方案2则温和点,但是仔细看下来,gateway并不是在承担“校验登录态权限”的职责了,他只是承担了“登录态换取用户信息”的职责,具体登录态权限仍然交由Service层完成。“查询推荐好友”有无登录态的case可以放到一个服务中实现了。
回过头来看看两个方案,哪个更为合理?gateway是否应当承担“校验登录态权限”的职责?还是只需承担一个“登录态换取用户信息”的职责?
如果让gateway承担“校验登录态权限”的职责,那么“查询推荐好友”实现起来就会比较复杂,要么客户端调用两次接口,或者将登录态tickitId作为一个request参数放在body中向后透传,由Service层去SessionServer校验登录态,但这明显是一种退步。
如果只是让gateway承担“登录态换取用户信息”的职责,可以获得前面所述的好处:降低对SessionServer的请求量,同时降低各个Service组件对SessionServer的耦合,而且对业务场景没有限制,不会过多的干预业务逻辑。
从头理解,我们会发现,“是否需要校验登录态”本身就应当交由业务Service来完成,只有业务Server才清晰知道哪些场景需要校验,哪些场景不需要校验。
故而,最终我们得到的方案为: