Netty_UDP丢包解决

程序背景

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

现象

客户端大数据量持续发UDP数据,作为UDP服务器出现了部分数据频繁丢失触发程序自身重传逻辑。

通过GC日志对比发现丢包的时间点偶有处于Full GC,说明Java程序接收间歇性stop world的不是根因。

观察Udp的dump

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

  • /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输出

  • 输出里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,代码如下

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在我们的测试场景下已经足够。优化后虽有改善但仍有丢包现象。

Linux系统级调优

定论:

应用层设置了UDP socket缓冲区不一定在Linux上生效,原因在于Linux对Udp socket缓冲区存有系统级限制,超过该限制的缓冲区大小无效。

Windows对socket的缓冲区没有限制

要点分析:

Linux通过net.core.rmem_max控制Udp的读缓冲区,通过net.core.wmem_max控制Udp的写缓冲区。

在程序的启动sh脚本里添加如下代码修改net.core.rmem_max

# 服务器默认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 Linux

上一篇:实现简单的printf函数


下一篇:Javascript的历史