Netty之UDP丢包解决

程序背景

程序是Java编写,基于Netty框架写的客户端及服务端。html

现象

客户端大数据量持续发UDP数据,做为UDP服务器出现了部分数据频繁丢失触发程序自身重传逻辑。
经过GC日志对比发现丢包的时间点偶有处于Full GC,说明Java程序接收间歇性stop world的不是根因。java

观察Udp的dump

经过watch -n 1 -d 'cat /proc/net/udp >> /usr/udpDump.txt'在发送数据的过程当中持续观察Udp缓冲区的情况linux

  • /proc/net/udp是瞬时的Udp socket dump,另有/proc/net/udp6用于监控IPv6
  • dump输出里的tx_queue是发送缓冲区,rx_queue是接收缓冲区,单位都是byte
  • 若是应用层收发效率足够好,正常状况下tx_queuerx_queue二者永远是0
  • 发送数据过程当中频现rx_queue>0,说明Udp缓冲区有堆积现象
  • 输出解释见How to monitor Linux UDP buffer available space?Meaning of fields in /proc/net/udp

观察Udp的stats

经过watch -n 1 -d 'netstat -su >> /usr/udpStats.txt'持续观察Udp的stats输出git

  • 输出里packets received的值指应用层从读入缓冲区里取走的包
  • 输出里packets to unknown port received的值指端口无应用监听而分发至该端口的包
  • 输出里packet receive errors的值指Udp接收错误数,正常状况下应该是0,在观察中不停增长,证实出现Udp包溢出接收缓冲区的状况
    • 发生错误的包数与接收错误数非一一对应
  • 资料参见Udp Packet Receive ErrorsUdp packet drops and packet receive error difference

解决问题

服务端代码优化

定论:
默认的UDP socket读缓冲区不够引起系统丢弃UDP包。
服务端代码优化设置UDP socket读缓冲区为2M,代码以下github

Bootstrap selfBootStrap = new Bootstrap();
selfBootStrap.group(group);
selfBootStrap.channel(NioDatagramChannel.class);
selfBootStrap.option(ChannelOption.SO_BROADCAST, true);
// 这一行设置了UDP socket读缓冲区为2M
selfBootStrap.option(ChannelOption.SO_RCVBUF, 1024 * 2048);
selfBootStrap.handler(channelInitializer);
selfBootStrap.localAddress(selfPort);

理论上Udp socket读缓冲区设置为2M在咱们的测试场景下已经足够。优化后虽有改善但仍有丢包现象。bash

Linux系统级调优

定论:
应用层设置了UDP socket缓冲区不必定在Linux上生效,缘由在于Linux对Udp socket缓冲区存有系统级限制,超过该限制的缓冲区大小无效。服务器

Windows对socket的缓冲区没有限制框架

要点分析:
Linux经过net.core.rmem_max控制Udp的读缓冲区,经过net.core.wmem_max控制Udp的写缓冲区。
在程序的启动sh脚本里添加以下代码修改net.core.rmem_maxsocket

# 服务器默认UDP读缓冲区最大128K。修改成2G。解决UDP丢包问题
rmemCount=`cat /etc/sysctl.conf|grep "net.core.rmem_max" | wc -l`
if [ ${rmemCount} -eq 0 ]
then
    echo "net.core.rmem_max = 2147483647" >> /etc/sysctl.conf
    sysctl -p
fi

脚本的做用就是修改/etc/sysctl.conf文件,并键入sysctl -p命令使自定义参数生效。
资料参见Improving UDP Performance by Configuring OS UDP Buffer LimitsUDP Drops on Linuxide