“查询推荐好友”是一个典型的社交服务场景,通常的需求是:用户未登录情况下,系统返回推荐好友列表: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的耦合,而且对业务场景没有限制,不会过多的干预业务逻辑。