一、前言
我们搭好了网关和一个基础微服务(含用户体系、门店服务、商品服务、客户服务),然后用APIfox测试过程中发现通过网关入口请求某些接口,一段时间后返回错误,查看系统日志发现除了报There is no session with id [f810df9f-74c4-4f26-a742-e770808acd38],没有其它任何异常,因为这个类似错误在以前系统里也有报,但不影响正常使用,直观认定这个不是造成问题的原因,然后进行了一系列排查(骚操作)。
二、排查过程
权限机制:我们在网关统一做认证和鉴权,简单讲就是在每个微服务中使用Spring的工具类将路径和Shiro注解解析出来然后注册到Redis中,网关这边根据请求路径及权限要求与当前用户所拥有的权限进行校对。
1、怀疑namespace配置问题
我们采用了namesapce进行环境隔离,见《SpringCloud Nacos环境隔离》,认为可能是namespace配置不合理造成网关无法访问基础微服务,将namespace配置移除,gateway重启后,一段时间后问题又出现。
2、怀疑Nacos的负载均衡配置
但因为我们测试环境,微服务是单机并不是集群部署的,怀疑单机配置了这个负载均衡造成无法访问,移除掉代码,重启服务,一段时间后问题又出现。
3、怀疑内存问题
虽然在日志中没有发现OOM,但观察到gc日志在40分钟左右heap会升到300多M,然后GC也比较频繁(YGC),怀疑是我们网关中一个定时任务(每隔15秒从Redis中注册的权限(路径+权限标识)同步到本地Map)占用了大量资源,这个定时任务在我们应用场景中必要性并不大,属于过度设计了,先摘掉再说,YGC频度降下来了,但问题依旧。
4、怀疑Gateway被攻击
Gateway低版本存在被攻击的可能性,查看日志发现没有攻击相关日志,并且我们还在测试环境,理论上被攻击的概率还是较低的。
5、重新定位问题
接口测试操作流程:在APIfox中先用账号A登录,返回了token,然后用token去请求其它接口,开始是正常的,一段时间后接口请求不通。让测试在测试不通的时候换另外一个账号B登录,然后用B的token去请求接口,正常。这时又重新怀疑是Shiro的问题,并且报错信息中有shiro报错信息There is no session with id。严重怀疑有shiro的session机制有关,那就有必要先了解一下Shiro Session机制。
三、网关中Shiro session
网关过滤器中代码如下:
注:每次拦截时都进行了一次subject.login,这本身并不合理,Review代码的时候我关注到了,但觉得问题不大,没较真,正常应该是在登录代码里进行subject.login,这里暂且先不管它。
1、subject.login 机制
它会去创建Shiro的Session,并且生成一个UUID型的SessionID.内部序列图如下
2、Session失效机制
每次调用subject.login都会重新生成一个Session(SessionId不同),Shiro默认Session失效是30分钟,这与我们开始应用重启过个几十分钟状况吻合,自己可以调整设置,为了重现问题,我将其调整为1秒。
3、拦截器中subject对象观察
两次请求拦截器是线程是复用的(这是gateway的WebFlux机制,需要深入学习一下),然后通过代码
SecurityUtils.getSubject()获取当前主体,这个是从ThreadLocal中获取。
这样主体subject一直没变但Session已经变更了,去验证的时候就会报SessionID不存在,这里最简单的处理方案是在后面再加上一行代码,验证通过后直接将主体销毁。我们做微服务主要的用户是收银台,但收银台账号不需要走Shiro这套机制,后台管理人员需要Shiro鉴权但这个访问量很小,每次请求都走一下Shiro登录,然后验证权限,最后Shiro登出问题也不大。