`

Netty OOM案例

阅读更多

Netty OOM案例

2015-10-23 林之风 Netty之家

问题:最近公司某产品商用发布在即,连续性能测试1个小时左右,开始发生时延变大、应答消息丢失等问题,最后抛出OOM异常,服务端宕机。

异常日志如下:

 



问题分析

 

通过异常堆栈和HeapAnalyzer工具分析,发现是Netty的内存池直接内存溢出,由于业务的消息接收和发送ByteBuf都使用了内存池直接内存,首先排查消息接收ByteBuf,业务处理流程如下:

1、业务的解码器继承自LengthFieldBasedFrameDecoder,根据报文中的消息长度做半包解码,解码成功之后将消息投递到后端业务线程池;

2、业务没有主动释放消息接收ByteBuf, 由于Netty解码之后会主动释放ByteBuf,所以不主动释放也没问题




排查完消息接收之后,再查看消息发送。消息发送流程是对请求消息包装之后,编码转发给其它第三方模块,消息发送采用了1个独立的发送线程,在发送线程中通过Netty的NioSocketChannel直接write ByteBuf,ByteBuf在发送线程中分配,发送完成之后没有调用release方法主动释放内存。

示例代码如下:

 


 

execut.execute(new Runnable() {

@Override

public void run() {

ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer(1024 * 1024);

CodeC.endcode(sendMessage, buf);

channel.write(buf);

//后续业务逻辑处理,没有主动释放内存

}

});


 

在业务线程中通过内存池申请了一个直接内存,编码发送之后并没有主动释放内存,是否有问题? 我们继续看Netty的源码:




通过代码分析,我们发现当Netty的ChannelOutboundBuffer将ByteBuf发送之后,会将ByteBuf从Entry[] buffer 中删除,同时调用safeRelease方法将ByteBuf释放。即便业务代码不主动释放发送的ByteBuf,Netty也会帮助用户释放,不应该发生内存泄漏啊?!

 

 

查看业务的Netty版本,发现业务使用的是Netty 4.0.X版本,突然想到了前段时间Netty 4内存池泄漏问题:在业务线程中通过内存池申请内存,又在Netty的NIO线程中释放内存,这会导致内存泄漏。该问题是Netty 4内存池机制和线程模型优化导致的问题,原理如下:

 




使用Netty 4.X +版本的内存池,内存的申请和释放必须要在同一个线程中,否则会导致内存引用错乱、内存溢出等问题。

 


 

问题定位出来之后,将内存池ByteBuf申请的代码迁移到ChannelHandler的CodeC中,由Netty的NIO线程统一申请和释放。优化之后,性能测试72个小时,内存占用平稳、GC正常,问题解决。

 

随后进行压力测试,客户端启动N个线程,使用同一个SocketChannel对服务端进行压测,24小时之后又发生了OOM异常,分析之后仍然是内存池的直接内存泄漏,怎么回事?

 



通过定位发现,在压力测试模式下,消息发送速度大于消息接收处理的速度,也就是说ByteBuf的申请速度大于释放速度,这导致了内存池不断膨胀,最终内存溢出。

 

如何解决这个问题? 业务建议通过调大服务端work线程数的方式提升服务端并行处理性能,但实际行不通。因为对于单链路场景,1个链路只被某一个work线程处理,增加work线程是没有效果的。

 

既然通过增大服务端线程数无法解决问题,那有没有更好的解决办法?方法有三个:

1、放弃内存池,使用非内存池模式;

2、动态流量控制;

3、采用多链路的方式。

 

使用非内存池模式,内存最终被JVM回收,而不是缓存在线程中,因而只要堆内存设置合适就可以解决内存溢出问题。


动态流控方案:可以使用Netty默认提供的流量整形功能,它可以解决两个问题:

1、防止由于上下游网元性能不均衡导致下游网元被压垮,业务流程中断

2、防止由于通信模块接收消息过快,后端业务线程处理不及时导致的“撑死”问题

 

原理如下:



多链路方案:通过调大服务端work线程个数,提升服务端的并行处理性能,满足高峰期的浪涌冲击。



案例总结

 

尽管Netty使用起来比较简单,但是如何在高并发和负载情况下保证系统平稳运行,却是并非一件易事。

除了完善的性能测试、压力测试之外,对Netty底层处理机制的理解和Code Review也是必不可少的。

分享到:
评论
1 楼 lixia0417 2018-04-28  
楼主,你的图片不见了。也就是说ByteBuf的申请速度大于释放速度,这导致了内存池不断膨胀,最终内存溢出,楼主这句话怎么理解,如果申请速度一直大于释放速度,那么即使使用非内存池也于事无补的吧

相关推荐

Global site tag (gtag.js) - Google Analytics