关于Netty发送消息堆积问题
最近一个H5游戏上线,出现堆外内存暴涨问题,于是进行了简单的分析,发现暴涨原因竟是服务器消息发送不出去,导致Netty发送队列堆积,内存上涨,针对此次分析,做一些总结。
SO_SEND_BUF和SO_REC_BUFF
- SO_SEND_BUF是操作系统内核的写缓冲区,所有应用程序需要发送到对端的信息,都会放到该缓冲区中,等待发往对端
- SO_REC_BUFF是操作系统内核的读缓冲区,所有对端发过来的数据都会放到该缓冲区中,等待应用程序取走
ChannelOutboundBuffer
该buffer是Netty等待写入系统内核缓冲区的消息队列
Channel的高低水位线
netty中提供一种水位线的标志,提用户当前通道的消息堆积情况。
用下面的代码来判断是否达到高水位线123if (!ch.isWritable()) { System.out.println("channel到达高水位.");}
Netty发送消息的流程
- 1.调用Channel的write方法,该方法会将消息加入ChannelOutboundBuffer,此时并没有实际发送.
当消息调用channel的write以后,netty回增加该连接发送队列的水位线
以下是AbstractChannel.java中的代码片段
|
|
- 2.调用Channel的flush方法,该方法将ChannelOutboundBuffer中的消息写入内核缓冲区
AbstractChannelHandlerContext.flush方法,准备写入。
|
|
NioSocketChannel.doWrite方法,该方法调用java原生nio将数据写入内核缓冲区。写入完毕,将消息从ChannelOutboundBuffer移除
并且减少ChannelOutboundBuffer的水位线。
|
|
线上问题
由于有大量广播消息,导致逻辑消息大量堆积在ChannelOutboundBuffer中,本身Netty的ByteBuf默认是使用堆外内存,所以内存不断暴涨。
具体堆积过程:消息发送慢->内核缓存区满->netty缓存区满->内存暴涨。
在调试过程中,发现H5客户端是单线程,所以如果客户端渲染之类的卡的话,也会导致消息不能及时被消费。
预计解决思路
- 1.全面降低功能系统的消息发送量,尽量精简消息结构
- 2.根据逻辑合理调整高水位线
- 3.当超过高水位线的时候,应该做一些策略,延缓消息发送,例如有条件的丢弃某些消息,防止内存暴涨