问题出现
公司的平台base服务最近会在运行一段时间后oom,当发现这问题后,我们在测试环境中尝试复现这个问题。
排查过程
首先idea中配置jvm参数 -XX:+HeapDumpOnOutOfMemoryError
,这个指令会在oom的时候打印一份dump文件。
在漫长的等待后终于复现了这个问题。。。。
我们使用jvisualvm和MAT这两个工具对生成的dump文件进行分析:
可以发现problem suspect提示StandardManager类占了堆内存的将近80%,继续深入排查发现是sessions出现了问题,存了大量的session数据,这些数据无法被回收,从而导致oom。
通过查看StandardManager和他的父类的相关源码我们可以了解到StandardManager里的sessions集合用来存放用户session数据,平常获取session是用的是HttpServletRequest#getSession()方法获取的,其真正调用的方法为org.apache.catalina.connector.Request#doGetSession
这里就会发现如果doGetSession获取的session为null的话,最终会调用org.apache.catalina.Manager#createSession
方法
而这个方法会创建一个session,并将其放入到sessions中维护
那么我们可以推断出是有接口不断地调用了 httpServletRequest.getSession()
,而上边的doGetSession方法为null,使得不断的创建新session导致了oom。
找到了问题的根源后我们去排查了项目中使用session的地方:
这是访问服务http接口需要走的鉴权拦截器的部分代码。
大概做了几件事:
- 先用token鉴权
- 通过后获取session里的用户信息和权限
我们反复排查发现传入token确实为用户正确的token,但在使用getSession的时候获取的sessionId却不是登录时存的sessionId。
经查阅发现session在IP相同的情况下为同一个作用域,而线上环境是所有服务部署在同一台服务器上的,所以平台的基本功能是可以使用的,也不会有多创建session的情况。
而对于srs视频服务来说,因为使用了nginx做转发,属于不同作用域,所以每开启一次视频,每一次视频保活都会创建新的session,从而导致线上oom,这也是问题很难定位的原因。
而对于线下环境来说,前后端分离的情况下前端ip和后端ip不同,所以基本功能的使用也会不断的创建session。
解决方法:
在分布式服务的环境下使用分布式session的解决方案来替代,比如用redis替代session存放数据
问题总结:
- httpServletRequest获取session的时候,如果前端有传,那么会使用传入的。如果没有传会创建一个新的。
- 在分布式情况下要注意作用域的问题,使用分布式session来替代。