导致Netty内存泄漏的原因有很多,例如,使用内存池创建的对象忘记释放,或者对端系统服务压力过大导致发送队列积压。
尽管Netty采用NIO非阻塞通信,I/O往往不是系统性能的瓶颈,但是如果服务端处理速度有限,客户端发送数据量很大,没有做好流控,同样会导致内存溢出。
对某个业务做性能压测,基于Netty开发的多个客户端并发链接一个服务端,客户端运行一段时间后,内存、CPU占用率居高不下,响应越来越慢,最后自动宕机。
为了方便分析,这里简化代码,这里用一个死循环向服务端发送消息,模拟压测环境,客户端代码如下:
业务调用ChannelHandlerContext.write方法后,经过ChannelPipeline责任链处理,消息被投递到了发送缓冲区中待发送,调用flush后才真正发送。
writeAndFlush方法,内部调用的是write方法,代码如下
跟进write()方法,处理逻辑如下,首先判断当前线程是否是NioEventLoop,如果不是,将发送的数据封装成一个WriteTask,放入NioEventLoop的任务队列由NioEventLoop线程执行。
在execute里做同样的判断后,在这里走的是else分支,调用addTask()后,将任务添加到任务队列中
Netty的NioEventLoop线程内部维护了一个Queue<Runnable> taskQueue,除了处理网络I/O读写事件,同时还负责网络读写相关的Task。
经过一系列处理后,消费端在拿到数据后,最终会调用ChannelOutboundBuffer的addMessage方法,将消息加入到发送队列。学过数据结构的同学,可以很清楚的看到,这个发送队列是基于链表组织起来的。
请注意方法结尾调用的incrementPendingOutboundByte方法,会在后面分析。文章开头描述的现象与此方法有关。
为了防止高并发场景下,由服务端处理慢导致客户端消息积压,除了服务端做流控外,客户端也需要做流控,自身保护,方法是,设置待发送队列的高低水位。
方法有两种,第一种是在启动类里设置option属性
第二种是
当发送队列到达高水位时,对应的Channel就会变为不可写状态。由于高水位并不影响业务线程调用write方法把消息写入待发送队列,因此必须在消息发送时对Channel的状态进行判断。
为了对待发送队列发送速度的控制,Netty提供了高低水位的机制,当积压消息量达到高水位时,修改Channel为不可写状态,在ChannelOutboundBuffer类
修改Channel状态后,调用ChannelPipeline发送通知消息
当积压消息发送完成后,对低水位进行判断,如果待发送字节数到达或低于低水位,修改Channel状态为可写,并发送通知事件。代码如下
针对上述分析后,再回头看文章开头描述的问题,我们修改代码为如下格式然后再压测,允许一段时间后 ,系统稳定。
内存消耗如下,可见运行非常稳定
以上是“Netty发送队列积压导致内存泄露怎么办”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注恰卡编程网行业资讯频道!