https://blog.csdn.net/feiyingHiei/article/details/78735754?utm_source=blogxgwz9
有源码分析
在启动Netty bootstrap的时候可以设置ChannelOption选项,其中ChannelOption中有一项WRITE_BUFFER_HIGH_WATER_MARK选项和WRITE_BUFFER_LOW_WATER_MARK选项,,此配置写缓冲区(OutbounduBuffer)相关,此配置可以帮助用户监控当前写缓冲区的水位状况,ChannelOutboundBuffer本身是无界的,如果水位控制不当的话就会造成占用大量的内存,今天准备结合代码来看看这个配置究竟是有什么作用。
ChannelConfig默认的水位配置为低水位32K,高水位64K,如果用户没有配置就会使用默认配置。
即使使用了默认配置,没有控制,则仍然会导致ChannelOutboundBuffer趋于无穷,水位只是提醒你,并不会操作
高水位的时候就会可以通知到业务handler中的WritabilityChanged方法,并且修改buffer的状态,channel调用isWriteable的时候就会返回false,当前channel处于不可写状态。
如果低于该水位就会设置当前的channel为可写,然后触发可读事件。
水位配置可以帮助我们监控缓冲区的使用情况,在写数据的时候需要判断当前channel是否可以继续向缓冲区写数据(isWriteable)。在之前的工作中出现过没有正确判断,而使用的编码器默认使用的又是堆外内存,导致在不断写入缓存的时候堆外内存超过jvm配置最大值。
https://my.oschina.net/u/3959468/blog/3018592 yet
通过以上分析可以看出,在直播高峰期,服务端向上万客户端推送消息时,发生了发送队列积压,引起内存泄漏,最终导致服务端频繁 GC,无法正常处理业务。
服务端在进行消息发送的时候做保护,具体策略如下:
根据可接入的最大用户数做客户端并发接入数流控,需要根据内存、CPU 处理能力,以及性能测试结果做综合评估。
设置消息发送的高低水位,针对消息的平均大小、客户端并发接入数、JVM 内存大小进行计算,得出一个合理的高水位取值。服务端在推送消息时,对 Channel 的状态进行判断,如果达到高水位之后,Channel 的状态会被 Netty 置为不可写,此时服务端不要继续发送消息,防止发送队列积压。
服务端基于上述策略优化了代码,内存泄漏问题得到解决。
当发送队列待发送的字节数组达到高水位上限时,对应的 Channel 就变为不可写状态。由于高水位并不影响业务线程调用 write 方法并把消息加入到待发送队列中,因此,必须要在消息发送时对 Channel 的状态进行判断:当到达高水位时,Channel 的状态被设置为不可写,通过对 Channel 的可写状态进行判断来决定是否发送消息。
https://www.jianshu.com/p/6c4a7cbbe2b5
在有些场景下,由于各种原因,会导致客户端消息发送积压,进而导致OOM。
这三种情况下,如果客户端没有流控保护,这时候就很容易发生内存泄露。
经过一些系统处理操作,最终会调用io.netty.channel.ChannelOutboundBuffer#addMessage方法,将发送消息加入发送队列(链表)。
https://www.jianshu.com/p/890525ff73cb
在Netty3的时候,upstream是在IO线程里执行的,而downstream是在业务线程里执行的。比如netty从网络读取一个包传递给你的handler的时候,你的handler部分的代码是执行在IO线程里,而你的业务线程调用write向网络写出一些东西的时候,你的handler是执行在业务线程里。而Netty 4修改了这一模型。在Netty4里inbound(upstream)和outbound(downstream)都是执行在EventLoop(IO线程)里。也就是你如果在业务线程里通过channel.write向网络写出一些东西的时候,在某一点,netty4会往这个channel的EventLoop里提交一个写出的任务。那也就是业务线程和IO线程是异步执行的。
因为序列化和业务线程异步执行,那么在write执行后并不表示user对象已经序列化了,如果这个时候修改了user对象那么传递到peer的对象可能就不再是你期望的那个user了。
我们就决定不再在handler里做序列化了,而是直接在业务线程里做。但是为了减少内存的拷贝,我们就期望在序列化的时候直接将字节流序列化到DirectByteBuf里,这样通过socket写出的时候就不进行拷贝了
是关于Netty里的ChannelOutboundBuffer这个东西的。这个buffer是用在netty向channelwrite数据的时候,有个buffer缓冲,这样可以提高网络的吞吐量(每个channel有一个这样的buffer)。初始大小是32(32个元素,不是指字节),但是如果超过32就会翻倍,一直增长。大部分时候是没有什么问题的,但是在碰到对端非常慢(对端慢指的是对端处理TCP包的速度变慢,比如对端负载特别高的时候就有可能是这个情况)的时候就有问题了,这个时候如果还是不断地写数据,这个buffer就会不断地增长,最后就有可能出问题了(我们的情况是开始吃swap,最后进程被linux killer干掉了)。为什么说这个地方是坑呢,因为大部分时候我们往一个channel写数据会判断channel是否active,但是往往忽略了这种慢的情况。那这个问题怎么解决呢?其实ChannelOutboundBuffer虽然无界,但是可以给它配置一个高水位线和低水位线,当buffer的大小超过高水位线的时候对应channel的isWritable就会变成false,当buffer的大小低于低水位线的时候,isWritable就会变成true。所以应用应该判断isWritable,如果是false就不要再写数据了。高水位线和低水位线是字节数,默认高水位是64K,低水位是32K,我们可以根据我们的应用需要支持多少连接数和系统资源进行合理规划。
原文:https://www.cnblogs.com/silyvin/p/12145700.html