Netty|Netty | 设置高低水位线避免 OOM

Netty 中的高低水位线是什么?

  • Netty 中的 Channel 都有一个写缓冲区;
  • 这个写缓冲区是 ChannelOutboundBuffer,这是个 Netty 发数据时的仓库,要发送的数据以数据结构 Entry 的形式存在仓库中,Entry 是个链表中的节点;
  • Netty 中的高低水位线,对应的就是这个链表中节点的数量范围,用于限制程序的写操作,自己在写程序的时候,需要用相应的代码给予配合,从而避免 OOM,增强写数据时的安全性;
  • 相关接口设计:Channel#config -> ChannelConfig#getWriteBufferHighWaterMark;
Netty OOM 的根本原因
  • 进(读速度) > 出(写速度);
Netty 发生 OOM 时的直接原因
  • 上游发送快;
  • 自己处理的慢;
  • 下游处理的慢;
  • 网速慢;
发生在 ChannelOutboundBuffer 的 OOM OOM 发生的原因
  • ChannelOutboundBuffer 相当于 Netty 发送数据的仓库,如果这个仓库存的数据特别多,就会发生 OOM;
  • 待写的数据以 ChannelOutboundBuffer.Entry 的数据结构存在 ChannelOutboundBuffer 中,Entry 是个链表中节点的结构,这个由 Entry 链起来的链表本身并没有控制大小,从而导致 OOM;
解决方式
  • 维护 Entry 链表的大小 totalPendingSize,如果 totalPendingSize 大于设定的高水位线 writeBufferWaterMark.high(),就设置标记位 unwritable;
  • writeBufferWaterMark 是关联在 Channel 的 ChannelConfig 中的;
发生在 ChannelTrafficShapingHandler 的 OOM OOM 发生的原因
  • 写暂停时 messagesQueue 中存的待写数据 ToSend 太多,就有可能发生 OOM;
解决方法
  • 判断 queueSize > maxWriteSize 或 delay > maxWriteDelay,如果满足任一条件,设置标志位 unwritable
unwritable 是什么?
  • 【Netty|Netty | 设置高低水位线避免 OOM】unwritable 是 ChannelOutboundBuffer 中的整型变量,一共 32 位,不同的位标识不同情景下的可写状态,为 0 标识可写,这样的设计考量是为了针对不同的场景做优化和处理,示例如图:

    Netty|Netty | 设置高低水位线避免 OOM
    文章图片
    unwritable.png
Netty OOM 的对策
  • 设置好高低水位线参数(默认 32 * 1024 ~ 64 * 1024);
ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.childOption(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 32 * 1024); bootstrap.childOption(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 8 * 1024);

  • 在启用流量整形的时候需要考虑设置:
    • maxWriteSize(默认 4M);
    • maxGlobalWriteSize(默认 400M);
    • maxWriteDelay(默认 4s);
最重要的!!!
  • 设置好了参数,如果自己在写代码的时候,没有做判断 channel.isWritable() 的,就跟没设置一样!!!
  • 示例代码片段:
// 这是对设置的高低水位线参数的尊重,如果设置了高低水位线,这里却不做判断,直接写,就有可能 OOM; if (ctx.channel().isActive() && ctx.channel().isWritable()) { ctx.writeAndFlush(responseMessage); } else { log.error("message dropped"); }

    推荐阅读