“packet receive errors”持续增长说明数据包在进入内核协议栈前被丢弃,大概率卡在网卡ring buffer或驱动层,常见原因包括rx队列过小、socket接收缓冲区不足、中断集中、全连接队列溢出或qdisc丢包。
查 netstat -s 里有没有大量 "packet receive errors"
有就说明数据包在进入内核协议栈前就被丢弃了,大概率卡在网卡 ring buffer 或驱动层。这时候 netstat -s 输出里会看到 udp: 或 tcp: 下的 packet receive errors、receive queue overflow 或 socket buffer overflows 持续增长。
常见原因和操作建议:
- 网卡接收队列(RX ring)太小,突发流量直接溢出:用
ethtool -g eth0查当前大小,再用ethtool -G eth0 rx 4096扩大(注意驱动是否支持,部分老网卡上限为 512) - 内核 socket 接收缓冲区不够:检查
net.core.rmem_max和net.core.rmem_default,Go 程序若没显式调SetReadBuffer,就依赖系统默认值(常为 212992 字节),高吞吐视频流容易打满 - 中断集中在一个 CPU 核上:
cat /proc/interrupts | grep eth0看 INT# 是否只集中在某几个 CPU,需用irqbalance或手动绑定 RPS/RSS
看 /proc/net/snmp 里 TcpExt 的 ListenOverflows
Go 视频服务如果用 net/http 或 net.Listen 启 HTTP/RTMP 服务,但连接建立后处理慢,就会堆积在全连接队列(accept queue)。此时 /proc/net/snmp 中 TcpExt: 行的 ListenOverflows 会持续增加,代表 SYN_RECV → ESTABLISHED 转换失败,新连接被内核静默丢弃。
关键参数和动作:
-
net.core.somaxconn控制全连接队列长度,默认常为 128,视频服务并发连接多时必须调大(如设为 4096) - Go 程序
net.Listen第二个参数是 backlog,它不能超过somaxconn,否则会被截断——很多 Go 项目写net.Listen("tcp", ":8080", 1024)却忘了改内核参数 - 检查是否启用了
net.ipv4.tcp_abort_on_overflow:设为 1 会导致连接直接 RST,设为 0 才会静默丢弃(更利于观测ListenOverflows计数)
用 ss -i 看单个 socket 的 sk_wmem_queued 和 sk_rmem_alloc
对 Go 进程中某个活跃连接(比如一个正在推流的 RTMP 客户端),执行 ss -tini src :1935(假设 RTMP 服务监听 1935),重点看输出里的 sk_wmem_queued(发送队列积压字节数)和 sk_rmem_alloc(接收缓冲区已用字节数)。这两个值长期 > 1MB,基本可判定是应用层消费/发送太慢,不是内核瓶颈。
Go 侧典型问题:
- 未启用
SetWriteBuffer/SetReadBuffer,缓冲区太小导致频繁系统调用和阻塞 - 使用
bufio.Reader但ReadSlice或ReadString配置不当,遇到长帧或粘包反复拷贝 - goroutine 泄漏:每个连接起一个 goroutine 处理,但异常退出后没 close conn,导致 socket 一直挂在内核里,
sk_rmem_alloc不释放 - 没设
SetDeadline,某个卡住的连接把整个 goroutine 拖死,后续连接全排队
确认是否真被 qdisc 限速或丢包
Go 视频转发若做了流量整形(如用 tc qdisc add dev eth0 root tbf rate 10mbit),得先排除人为限速。但更隐蔽的是:当网卡驱动或内核 qdisc 队列满时,会主动丢包,tc -s qdisc 输出里的 drops 字段会非零。
排查要点:
- 运行
tc -s qdisc show dev eth0,关注dropped和requeues;若dropped > 0且持续增长,说明 qdisc 层已开始丢包 - 默认的
fq或pfifo_fast队列长度由txqueuelen决定:ip link show eth0看txqueuelen值(常为 1000),高码率视频流易打满,可临时加大:ip link set eth0 txqueuelen 5000 - Go 程序若用
sendfile(如io.Copy配合文件句柄),要注意内核版本:5.3+ 支持copy_file_range更高效,老内核 fallback 到用户态拷贝,CPU 占用飙升还拖慢转发
真正卡在内核网络栈的场景其实不多,多数是 Go 应用层缓冲、goroutine 管理或系统参数没对齐造成的假性瓶颈;盯住 netstat -s、/proc/net/snmp 和 ss -i 这三个地方的计数器变化,比盲目调 sysctl 有效得多。











