千家信息网

Netty发送队列积压导致内存泄露怎么办

发表于:2024-12-04 作者:千家信息网编辑
千家信息网最后更新 2024年12月04日,这篇文章主要为大家展示了"Netty发送队列积压导致内存泄露怎么办",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"Netty发送队列积压导致内存泄露怎么办"
千家信息网最后更新 2024年12月04日Netty发送队列积压导致内存泄露怎么办

这篇文章主要为大家展示了"Netty发送队列积压导致内存泄露怎么办",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"Netty发送队列积压导致内存泄露怎么办"这篇文章吧。

正文

导致Netty内存泄漏的原因有很多,例如,使用内存池创建的对象忘记释放,或者对端系统服务压力过大导致发送队列积压。

尽管Netty采用NIO非阻塞通信,I/O往往不是系统性能的瓶颈,但是如果服务端处理速度有限,客户端发送数据量很大,没有做好流控,同样会导致内存溢出。

对某个业务做性能压测,基于Netty开发的多个客户端并发链接一个服务端,客户端运行一段时间后,内存、CPU占用率居高不下,响应越来越慢,最后自动宕机。


为了方便分析,这里简化代码,这里用一个死循环向服务端发送消息,模拟压测环境,客户端代码如下:


Netty发送数据源码分析

业务调用ChannelHandlerContext.write方法后,经过ChannelPipeline责任链处理,消息被投递到了发送缓冲区中待发送,调用flush后才真正发送。

writeAndFlush方法,内部调用的是write方法,代码如下


跟进write()方法,处理逻辑如下,首先判断当前线程是否是NioEventLoop,如果不是,将发送的数据封装成一个WriteTask,放入NioEventLoop的任务队列由NioEventLoop线程执行。


在execute里做同样的判断后,在这里走的是else分支,调用addTask()后,将任务添加到任务队列中


Netty的NioEventLoop线程内部维护了一个Queue taskQueue,除了处理网络I/O读写事件,同时还负责网络读写相关的Task。


经过一系列处理后,消费端在拿到数据后,最终会调用ChannelOutboundBuffer的addMessage方法,将消息加入到发送队列。学过数据结构的同学,可以很清楚的看到,这个发送队列是基于链表组织起来的。


请注意方法结尾调用的incrementPendingOutboundByte方法,会在后面分析。文章开头描述的现象与此方法有关。

如何防止发送队列积压

为了防止高并发场景下,由服务端处理慢导致客户端消息积压,除了服务端做流控外,客户端也需要做流控,自身保护,方法是,设置待发送队列的高低水位。

方法有两种,第一种是在启动类里设置option属性

第二种是

当发送队列到达高水位时,对应的Channel就会变为不可写状态。由于高水位并不影响业务线程调用write方法把消息写入待发送队列,因此必须在消息发送时对Channel的状态进行判断。


为了对待发送队列发送速度的控制,Netty提供了高低水位的机制,当积压消息量达到高水位时,修改Channel为不可写状态,在ChannelOutboundBuffer类


修改Channel状态后,调用ChannelPipeline发送通知消息


当积压消息发送完成后,对低水位进行判断,如果待发送字节数到达或低于低水位,修改Channel状态为可写,并发送通知事件。代码如下


针对上述分析后,再回头看文章开头描述的问题,我们修改代码为如下格式然后再压测,允许一段时间后 ,系统稳定。


内存消耗如下,可见运行非常稳定

以上是"Netty发送队列积压导致内存泄露怎么办"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

0