1.问题背景
线上某个工程总是会出现OOM导致系统宕机
2.排查追踪
拿到dump文件使用MAT工具分析
在dominator_tree中,AbstractProtocol类占据了绝大部分堆内存
看到AbstractProtocol类猜测可能和协议有关,HTTP、TCP、SMTP、WebSocket等
继续展开,可以发现WsFrameServer的类占据了大部分堆内存,定位为WebSocket协议导致的内存溢出。
再继续展开,可以发现HeapCharBuffer占据了大部分的内存。
如果项目中只有一个ws功能则就可以定义到具体业务了,如果有多个功能模块涉及ws则需要根据ws地址确认功能。
通过requestUri可以查看到ws地址和请求ip
通过上面排查是messageBufferText过大且链接过多导致的内存溢出,查看messageBufferText大小约20MB,则需要搞明白下面的问题:
- 20MB是否是初始化大小?
- 如果不是初始化大小,在哪里被赋值/配置修改?
- 如果是初始化大小,在哪里可以赋值/配置修改?
那就需要查看源码分析messageBufferText是怎么初始化大小的。
首先找到了WsFrameServer类,发现messageBufferText是WsFrameServer父类WsFrameBase的成员变量
WsFrameBase类
查看messageBufferText初始化:
CharBuffer.allocate静态方法创建CharBuffer的对象,其中会初始化一个大小为getMaxTextMessageBufferSize的Char类型的数组。
下面主要看wsSession.getMaxTextMessageBufferSize()方法
wsSession.getMaxTextMessageBufferSize()可以看到获取的是maxTextMessageBufferSize的数值
查看maxTextMessageBufferSize赋值
有一个默认值是8K,显然与想要的20MB差距比较大,继续看哪里有赋值了
wsSession.setMaxTextMessageBufferSize()
查看setMaxTextMessageBufferSize方法的调用
在代码实现里增加了这个配置,由于char类型占两个字节,1010241024*2 约 20MB,所以这个就是原因
不过我们继续看另外一个调用PojoMessageHandlerWholeText
往下追踪发现默认是-1,注解OnMessage的maxMessageSize字段获取值。
3. 解决思路
- 临时解决: 现场临时增加内存分配空间
ps:默认情况下,-Xms是物理内存的1/64,-Xmx是物理内存的1/4 - message初始值大小: 统计ws的maxTextMessageBufferSize大小,设置合理的数值,或考虑压缩message大小。
- 产品层面: 问题同步至产品,从产品层面优化。
如:从根据可用内存对ws链接数做最大限制 - 服务优化:
如:由于在项目中ws是长链接,及时释放不再使用的WebSocket链接(超时)、同一个ip和ws地址使用同一个链接
超时功能:项目中使用nginx,可以在nginx中增加超时配置proxy_read_timeout - 运维层面: 增加健康检查对于意外崩溃自动重启服务