在netty decoder层控制heap内存占用,防止es oom
es使用netty来通信,实现分布式的功能,但在某些场景会oom。
1:netty概述
IO 多路复用
NioEventLoop.run select由jdk实现在win下使用select,在linux下使用epoll。
linux下也可以使用netty实现的epoll:EpollEventLoop。
线程模型
parent NioEventLoop[listen] [1]---- [n] children NioEventLoop[established] [1]----[n] channel [1]---- [1] channelPipeline[channelInboud/Outboundhandler list]。
1:每个NioEventLoop绑定一个线程。
2:parent NioEventLoop负责监听连接请求,并将建立连接请求轮询分配给childrenNioEventLoop。
3:一个child NioEventLoop通过select/epoll处理所负责的N个channels上的read,write事件。且一个channel上的read,write事件只能在该NioEventLoop线程上处理,并发安全。
4:具体处理时,由该channel对应的channelPipeline中的各个channelHandler顺序处理。
2:es对netty的使用
TCP/IP
1:一个request/response可能会因为分包(最大64k),对应产生多次channelRead/write。
2:一个数据包channelRead也可能因为tcp的延迟发送(粘包),而包括多个request,或者一个request的后半部分和下一个request的前半部分。
3:所以在channelPipeline中处理channelRead的数据包时,需要累积多个数据包数据组成一个完整的request,或者从一个数据包中拆分出多个request。
引起oom
当多个size很大的request,使用不同的channel,发送到不同的NioEventLoop处理时。
由于需要累积完整的数据包,才能交给下一个handler去反序列化成对象,进而处理请求。所以,多个channel中累计部分的request的数据时,可能会引起oom。
相关代码:
ByteToMessageDecoder
Netty4SizeHeaderFrameDecoder
Netty4MessageChannelHandler
Netty4Transport
TcpTransport
3:限流实现避免oom
增加总的计数器(breaker),读取request的第一个数据包,从中header中读取request的size,如果超过limit限制,则舍弃该request的本次,及后续的所有数据包。
注意
1:request的第一个数据包可能在一个新包的开头。也可能因为延迟发送,和其他request在一个数据包中。
2:累加器如果成功加上,则要读取完整size的request后,交给es处理请求,并保证处理后减去size。累加器如果超过limit,则需要从多个数据包中,舍弃size长度的数据,并交给es处理shardFailure。
3:可以设置request的status,指定只对某些大的结果(如:聚合)做限流。