运维这行,恐怕没几个人没被丢包问题折磨过。常见的症状五花八门:
- SSH 连上去了,但敲个命令,光标要愣好几秒才吐回显。
- Web 接口间歇性抽风,监控图上的 RTT 跟心电图似的周期性抖动。
- 数据库主从同步延时突然飙高,看日志全是
Read timeout。
- 跨机房拷个大文件,速度远低于带宽标称值,可一跑
iperf 又显示正常。
- 视频流 RTP 丢包率一高,播放端立马花屏。
- K8s 集群里,Pod 之间通信动不动就丢包,Service 转发也出状况。
丢包的根因,可能藏在网络栈的任何一个角落。从物理层的光模块、网线,到数据链路层的 CRC 错误,再到网络层的路由黑洞、MTU 不一致,上探到传输层的 TCP 拥塞、应用层的 Nginx backlog 设置……每一层都可能藏着"漏勺"。
很多同学排查思路很直接,上手就是一个 ping。通了就以为万事大吉,不通就当场卡壳。但 ping 只能证明 ICMP 协议是好的,代表性实在有限。真要解决问题,得从应用到底层,顺着网络栈一层层往下捋。
这篇文章基于 Linux 内核 4.19 及以上版本(主要覆盖 CentOS 8 / RHEL 8 / Ubuntu 20.04+ / Debian 11+ 等环境),系统梳理一把排查网络丢包的完整链路。主力工具涵盖 ping、traceroute、mtr、ss、netstat、sar、ip、ethtool、dmesg、tcpdump、dropwatch 和 conntrack。
适用场景
如果你正在面对下面这些"鬼故事",那这篇文章就是写给你的:
- TCP/UDP 业务偶发超时、卡顿甚至直接连接失败。
- 涉及跨机房、跨地域的链路质量评估。
- 排查 K8s 或 Docker 容器网络的丢包。
- 数据库主从同步延迟、Sentinel 心跳莫名丢失。
- VoIP、视频流等场景的 RTP 丢包。
- 高 QPS 服务下,请求丢失或收到莫名其妙的 RST 重置。
本文不谈具体的业务代码和内核源码深度剖析,只聚焦于能让你快速上手的工程实战。在动手之前,推荐先对 TCP/IP 协议栈 的基本原理有个大概印象,排查起来会更得心应手。
核心概念
丢包的“两个层面”
我们可以把丢包分成两类,这样能更快地锁定排查方向:
物理丢包:发生在物理链路、网卡硬件、交换机端口上。可以通过 ifconfig(或更推荐的 ip 命令)看到网卡的 RX errors、TX errors、dropped、overruns,也能查交换机的端口统计。物理丢包的原因很直白:网线不行了、光模块老化、端口协商异常、CRC 校验失败、网卡 ring buffer 被打满等等。
逻辑丢包:发生在内核协议栈内部。比如 IP 层路由失败、Netfilter 一把 DROP、TCP 层的 conntrack 表满了、socket buffer 溢出、应用层 accept 队列太小或进程来不及处理数据……逻辑丢包不一定会体现在网卡硬件计数器上,通常得去翻 /proc/net/ 下的各类统计或系统日志。
排查顺序
推荐的排查路径是自顶向下的:
- 应用层:先看业务日志、监控曲线、连接状态,有没有直接报错。
- 传输层:看 TCP 状态机、socket buffer、conntrack 表。
- 网络层:看路由表、MTU 设置、IP 层统计、Netfilter 规则。
- 数据链路层:看网卡统计、ethtool、交换机端口.
- 物理层:查光模块光衰、网线、端口指示灯。
千万别先入为主,每一层都老老实实拿对应工具过一遍。
关键指标
排查丢包时,这几项指标要重点关注:
- RTT(Round Trip Time):往返延迟,直觀反映链路质量和拥塞程度。
- 丢包率(Packet Loss Rate):单位时间内丢失数据包的比例。
- 重传率(Retransmission Rate):TCP 重传段数占总发送段数的比例。
- 乱序率(Out-of-Order Rate):TCP 包到达对端时顺序错乱的比例。
- 重复 ACK(Dup ACK):TCP 接收方发出的重复确认包数量,反映网络抖动。
- 带宽利用率:实际跑的流量和理论带宽的比值。
抓包与采样
抓包是排查网络问题的终极武器,但有几个大坑得提前绕开:
- 过滤一定要精确,不然磁盘分分钟被打满。
- 抓包本身吃 CPU,搞不好会加重线上业务压力。
- 先抓客户端还是先抓服务端,直接决定你的排查走向。
- 分析抓包文件,得熟 TCP 状态机、三次握手、四次挥手、TCP 重传、Dup ACK、快速重传、慢启动、拥塞控制等模型。
排查路径
第一步:确认丢包现象和影响范围
动手前先问自己几个问题,把范围缩到最小:
- 是单向丢(只丢上行或下行)还是双向丢?
- 是持续丢还是抽风式地间歇丢?
- 是特定的一条连接丢,还是这台机器所有连接都丢?
- 是针对特定源/目标 IP 丢,还是全丢?
凭业务监控和简单的 ping、mtr,一般就能有个初步判断。
# 简单 ping 测试(生产上慎用大包和短间隔)
ping -c 100 -i 0.2 <目标IP>
# 输出会直接告诉你丢包率和 RTT 统计
# --- 8.8.8.8 ping statistics ---
# 100 packets transmitted, 98 received, 2% packet loss
# round-trip min/avg/max = 10.5/15.2/100.3 ms
假如 ping 有丢,那就顺着 mtr 找是哪一跳开始的。如果 ping 丝滑无比但业务照样超时,那八成是纯 TCP 层面的问题,ping 抓不出来。
注意:有些机房对 ICMP 限速,ping 显示的丢包可能不是真的链路丢包,而是被设备限了。
第二步:使用 mtr 定位丢包路径
mtr 可以看作是 traceroute + ping 的整合版,能持续对每一跳进行探测,画出一条路径上的丢包率和延迟变化趋势,比一次性的 traceroute 有用得多。
# 安装 mtr
yum install mtr -y # CentOS / RHEL
apt install mtr-tiny -y # Ubuntu / Debian
# 基础用法(默认 ICMP Echo)
mtr -r -c 100 <目标IP> # -r 报告模式,-c 100 发100个包
# 详细模式(同时展示更多信息)
mtr -r -c 100 -e <目标IP> # -e 显示扩展信息
# TCP 模式(用于绕过某些 ICMP 限速的路由器)
mtr -T -P 443 -r -c 100 <目标IP> # -T TCP 模式,-P 443 端口
mtr --tcp --port 443 -r -c 100 <目标IP>
# UDP 模式
mtr -u -r -c 100 <目标IP>
mtr 输出里,要盯着每一跳的 Loss%(丢包率)、Avg(平均延迟)和 StDev(标准差)。核心判断逻辑:
- 丢包率在某一跳突然从 0 跳到 X%。
- 并且后面的所有跳,丢包率都大于等于这个 X%。这说明丢包就是从这一跳开始的。
- 如果某一跳丢包很高,但后面的跳反而丢得少甚至不丢了,那这一跳大概率只是对 ICMP 限速,不是真丢,此时果断切到 TCP 模式看。
坑点:mtr 的 ICMP 模式有时真会误导人。拿不准的时候,直接上 TCP 模式:
mtr --tcp --port 443 -r -c 100 <目标IP>
第三步:查看本机 TCP / UDP 统计
丢包排查必须把本机的协议栈统计翻出来看。/proc/net/snmp 和 /proc/net/netstat 里有 TCP、UDP、IP 的全套统计。
# 查看 TCP 统计
cat /proc/net/snmp | grep Tcp
# 输出大致如下:
# Tcp: RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpens PassiveOpens AttemptFails EstabResets CurrEstab InSegs OutSegs RetransSegs InErrs OutRsts
# Tcp: 1 200 120000 -1 12345 67890 123 456 789 1000000 800000 5678 0 12
需要紧盯的字段:
InSegs / OutSegs:收发的 TCP 段总数。
RetransSegs:重传的 TCP 段数,这是心脏跳动的脉搏。
InErrs:收到错误段的数量(如 CRC 错)。
OutRsts:发出的 RST 包数量。
CurrEstab:当前状态为 ESTABLISHED 的连接数。
重传率 = RetransSegs / OutSegs。只要这个值长期超过 1%,就得警惕了;如果超过 5%,网络质量或对端肯定有严重问题。
# 用 nstat 或 netstat -s 看实时统计,-a 计绝对值,-z 每次运行后清零
nstat -az | grep -E "(TcpExt|TcpIn|TcpOut)"
# 输出类似这样,可以看到很多细分指标:
# TcpRetransSegs 1234 0.0
# TcpOutRsts 56 0.0
# TcpInErrs 0 0.0
# TcpActiveOpens 789 0.0
# TcpPassiveOpens 1234 0.0
# IpInDiscards 0 0.0
# IpInDelivers 1000000 0.0
# IpOutRequests 800000 0.0
nstat 可以看作 netstat -s 的动态版本,适合盯着看每秒的变化量。IpInDiscards 是 IP 层直接丢的包数,很重要。
# 查看 UDP 统计
cat /proc/net/snmp | grep Udp
# Udp: InDatagrams NoPorts InErrors OutDatagrams
# Udp: 12345 0 12 6789
# InErrors 是 UDP 接收错误数,比如 checksum 不对,或者根本没有程序监听这个端口
第四步:查看网卡统计
物理层的丢包,会直接反映在网卡硬件计数器上。别再用 ifconfig 了,推荐用 ip 和 ethtool。
# 用 ip 命令看网卡统计
ip -s link show <网卡名>
# 输出类似:
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT
# link/ether 00:16:3e:xx:xx:xx brd ff:ff:ff:ff:ff:ff
# RX: bytes packets errors dropped missed mcast
# 12345678 10000 0 0 0 0
# TX: bytes packets errors dropped carrier collsns
# 87654321 9000 0 0 0 0
必须查看到的关键字段:
RX errors:接收错误,比如 CRC、帧错误。
RX dropped:接收丢包,ring buffer 满了或协议栈没接住。
RX missed:网卡硬件漏包,也是 ring buffer 满的典型标志。
TX errors / TX dropped:发送侧的错误和丢包。
TX carrier:载波错误,一般是物理链路问题。
# 用 ethtool 看网卡协商详情
ethtool <网卡名>
# 输出类似:
# Settings for eth0:
# Speed: 1000Mb/s
# Duplex: Full
# Auto-negotiation: on
# Link detected: yes
这里的 Speed 和 Duplex 必须和交换机端口协商结果一致。如果这里显示 1000Mb/s Full,但交换机口子是 100Mb/s,那协商都有问题,丢包也就不奇怪了。
# 查看网卡驱动级统计(更详细的计数器)
ethtool -S <网卡名>
# 输出会有很多,重点关注这些:
# rx_errors: 0
# tx_errors: 0
# rx_dropped: 0
# tx_dropped: 0
# rx_crc_errors: 0
# rx_frame_errors: 0
# rx_fifo_errors: 0
# rx_missed_errors: 0
# tx_aborted_errors: 0
# tx_carrier_errors: 0
# tx_fifo_errors: 0
不同网卡驱动的统计字段名可能不同,但 rx_fifo_errors 和 rx_missed_errors 几乎就是 ring buffer 被打满的代名词。
# 查一下当前 ring buffer 大小
ethtool -g <网卡名>
# Ring parameters for eth0:
# Pre-set maximums:
# RX: 4096
# TX: 4096
# Current hardware settings:
# RX: 256
# TX: 512
Current 是当前值,Pre-set maximums 是硬件支持的上限。如果当前值偏小且丢包计数在涨,可以尝试调大:
# 在线临时调整 ring buffer
ethtool -G <网卡名> rx 4096 tx 4096
# 永久生效需要写入网卡配置文件
注意:ring buffer 调得太大,会增加很小的延迟,但换来的是减少丢包,需要根据业务特性权衡。
第五步:查看 socket 状态
排查 TCP 丢包,ss 是最好使的工具,速度比老旧的 netstat 快多了。
# 看看所有 TCP 连接状态
ss -tan
# 查看 UDP socket
ss -uan
# socket 整体统计一览
ss -s
# 输出类似:
# TCP: 1234 (estab 1000, closed 200, orphaned 0, timewait 200)
常用的过滤和查看姿势:
# 看某个端口的连接
ss -tan 'sport = :80'
# 看连往某个 IP 的连接
ss -tan 'dst 8.8.8.8'
# 看看连接状态分布
ss -tan | awk '{print $1}' | sort | uniq -c
# 查看 socket buffer 等详细信息,-i 显示 TCP 内部信息
ss -tan -i
# 查看更详尽的 TCP 信息,包括 RTT、cwnd、拥塞窗口、重传等
ss -tan -i -e
ss -tan -i 的输出里,隐藏着每个连接的健康密码:
cwnd:拥塞窗口大小。
rtt:平滑后的往返时间。
rttvar:RTT 的抖动方差。
ssthresh:慢启动阈值。
retrans:此连接已发生的重传次数。
lost:丢失的包数。
通过这些字段,能很清楚看到每条连接的"体质"如何。
# 看下当前所有连接 RTT 的"高富帅"排行榜(末20位)
ss -tan -i | grep -E "rtt:" | awk -F: '{print $2}' | sort -n | tail -20
第六步:查看连接跟踪表
Linux 的 nf_conntrack 模块会跟踪每个 TCP/UDP 连接的状态。如果连接数暴增把表撑爆了,内核日志里就会出现“nf_conntrack: table full, dropping packet”,新连接直接被拒之门外。
# 查看连接跟踪表当前条目数
cat /proc/sys/net/netfilter/nf_conntrack_count
# 查看连接跟踪表的上限值
cat /proc/sys/net/netfilter/nf_conntrack_max
# 如果这俩数非常接近,说明表快满了,很危险
# 查看连接跟踪表统计,确认是否真有丢包
cat /proc/net/stat/nf_conntrack
# 输出类似这样,数据是十六进制:
# entries searched found new invalid delete delete_list insert insert_failed drop early_drop icmp_error expect_new expect_create expect_delete
# 00000000 0 0 0 0 0 0 0 0 0 0 0 0 0 0
如果 insert_failed 和 drop 这两列不为零,说明 conntrack 表已经出现丢包了。
调优思路:
# 1. 调大连接跟踪表上限(注意内存消耗)
sysctl -w net.netfilter.nf_conntrack_max=1048576
# 2. 适当缩短超时时间,加速回收
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=3600
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_close_wait=30
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_fin_wait=30
风险提醒:每条 conntrack 记录大概吃 300 字节内存,nf_conntrack_max 有多大,就可能吃掉多少内存,调大前务必算好内存量。
第七步:查看 IP 层丢包统计
# 直接看 IP 层的累计统计
cat /proc/net/snmp | grep Ip
# Ip: Forwarding DefaultTTL InReceives InHdrErrors InAddrErrors ForwDatagrams InUnknownProtos InDiscards InDelivers OutRequests OutDiscards OutNoRoutes ReasmTimeout ReasmReqds ReasmOKs ReasmFails FragOKs FragFails FragCreates
# Ip: 1 64 12345 0 0 0 0 0 12345 8000 0 0 0 0 0 0 0 0 0
紧盯这些字段:
InHdrErrors:IP 报文头坏了。
InAddrErrors:目标地址不可达。
InDiscards:IP 层直接丢弃的包(通常是因为内存不足或被 Netfilter 干掉)。
OutDiscards:发送时在 IP 层就扔了。
ReasmFails:IP 分片重组失败了。
假如 InDiscards 不为零,但 InHdrErrors 和 InAddrErrors 都是零,那十有八九是 IP 层内存吃紧或者被 iptables 规则 DROP 了。
第八步:查看 Netfilter / iptables 丢包
如果机器上跑着 iptables 规则,丢包可能发生在 Netfilter 的钩子点上。iptables 的 -j DROP 或 -j REJECT 自然不用说,而自带的计数器能帮我们快速定位是哪条规则在"作案"。
# 列出所有规则及精确的命中计数
iptables -L -nvx
# 输出举例:
# Chain INPUT (policy DROP 0 packets, 0 bytes)
# pkts bytes target prot opt in out source destination
# 100 5000 ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0
# 200 10000 ACCEPT all -- eth0 * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
# 5 200 DROP all -- eth0 * 0.0.0.0/0 0.0.0.0/0 state INVALID
如果那条 state INVALID 的 DROP 规则计数器在持续增长,很可能是对端发包异常,或者 conntrack 表挤出问题了。
# 到内核日志里找找 conntrack 的相关线索
dmesg | grep -i "conntrack"
dmesg | grep -i "table full"
第九步:查看内核 ring buffer 丢包
/proc/net/softnet_stat 记录了每个 CPU 核的 softirq 处理统计。
cat /proc/net/softnet_stat
# 每一行代表一个 CPU 核,列数视内核版本而定
# 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
关键字段(不同版本列号可能变化):
- 第 1 列:这个核处理的总包数。
- 第 2 列:丢包数!就是 netdev backlog 队列满了被扔掉的包。
- 第 3 列:softirq 处理耗时。
- 第 4 列:softirq 唤醒延迟。
只要第 2 列在涨,就得赶紧调大 net.core.netdev_max_backlog:
# 增大内核接收队列
sysctl -w net.core.netdev_max_backlog=65535
第十步:使用 tcpdump 抓包分析
如果前面这些"统计型"手段都定位不到,那就得上抓包这个核武器了。
# 在客户端抓(看发出去的包)
tcpdump -i eth0 -nn -s 0 -w client.pcap host <对端IP>
# 在服务端抓(看收到的包)
tcpdump -i eth0 -nn -s 0 -w server.pcap port <服务端口>
# 只抓特定协议和端口
tcpdump -i eth0 -nn -s 0 -w tcp.pcap tcp port 80
# 只显示包内容不存盘,过滤 SYN 和 FIN
tcpdump -i eth0 -nn -c 1000 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0'
# 抓 HTTP 请求/响应内容(ASCII 模式)
tcpdump -i eth0 -nn -A -s 0 port 80
核心参数要牢记:
-i:指定网卡。
-nn:不做 DNS 和端口名反向解析,直接显示 IP 和端口号。
-s 0:抓完整包,别只抓个头。
-w:把原始数据存成文件,后续用 Wireshark 慢慢分析。
-c N:抓够 N 个包就停,适合采样。
-A / -X:以 ASCII 或十六进制显示包体内容。
-v / -vv / -vvv:越来越啰嗦的详细输出。
BPF 过滤表达式是灵魂:
# 只关心某个 IP
tcpdump -i eth0 host 8.8.8.8
# 只关心某个端口
tcpdump -i eth0 port 80
# 精确到源或目标
tcpdump -i eth0 src host 8.8.8.8
tcpdump -i eth0 dst port 80
# 基于 TCP 标志位精准过滤
tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0'
tcpdump -i eth0 'tcp[tcpflags] & tcp-rst != 0'
tcpdump -i eth0 'tcp[tcpflags] & tcp-fin != 0'
# 组合拳
tcpdump -i eth0 'src host 8.8.8.8 and tcp port 80 and tcp[tcpflags] & tcp-syn != 0'
抓包后怎么分析才是重点:
# 抓取重传相关的包
tcpdump -i eth0 -nn -s 0 -w retrans.pcap 'tcp[tcpflags] & (tcp-ack) != 0 and not src host <本机IP>'
把 pcap 文件丢进 Wireshark 后,可以直接用内置的显示过滤器:
tcp.analysis.retransmission:重传的包
tcp.analysis.fast_retransmission:快速重传
tcp.analysis.duplicate_ack:重复的 ACK
tcp.analysis.lost_segment:推断丢失的段
tcp.analysis.out_of_order:乱序
Wireshark 的 TCP 分析功能可以帮你可视化地看到重传、乱序、窗口大小变化、RTT 趋势等,是分析 TCP 问题的绝对利器。
第十一步:使用 dropwatch 跟踪内核丢包点
dropwatch 是专门用来盯着内核丢包函数的小工具。
# 安装 dropwatch
yum install dropwatch -y
# 运行并开始监控
dropwatch -l kas
dropwatch> start
# 此时如果有内核丢包发生,它就会打印出来
输出类似这样,直接告诉你在内核的哪个函数丢的:
2 drops at tcp_v4_rcv+0x1a/0x950
1 drops at nf_conntrack_in+0x150/0x680
注意:dropwatch 依赖内核的 NET_DROP 监控点,某些精简过的内核可能不支持。
第十二步:使用 perf / ftrace 深入分析
如果 dropwatch 都没辙,祭出 perf 或 ftrace 来跟踪内核协议栈的详细执行过程,这属于高级玩法了。
# 用 perf 跟踪 kfree_skb 事件,skb 被释放往往意味着丢包
perf record -e skb:kfree_skb -a -g sleep 10
perf script
# 输出会展示每一次 skb 释放的调用栈,可以直接定位到丢包的内核函数
这方法对内核知识有一定要求,不过真能精确到代码行。
常见丢包场景
场景 1:UDP 丢包
UDP 本身就是无连接协议,不保证可靠性,丢包是它天性的一部分。但排查应用层感受到的丢包时,还是得走一遍流程。
# 看 UDP 层面的统计
nstat -az | grep -E "(Udp)"
# 关注这些:
# UdpInDatagrams: 收到的 UDP 包总数
# UdpOutDatagrams: 发出的 UDP 包总数
# UdpInErrors: 收包时出的错(checksum坏、端口不对等等)
# UdpNoPorts: 发到了没人监听的端口
# UdpInCsumErrors: checksum 错
# UdpRcvbufErrors: socket 接收缓冲区错误
# UdpSndbufErrors: socket 发送缓冲区错误
UDP 丢包的常见坑:
- 接收端 buffer 太小:UDP socket 的
SO_RCVBUF 默认可能不大,高并发下瞬间就打满。调大 net.core.rmem_default 和 net.core.rmem_max 能缓解。
- 应用层消费太慢:如果
recvfrom() 调用不及时,包全堆在 socket buffer 里,满了自然就丢了。
- 防火墙/conntrack 捣乱:UDP 流一旦超时,conntrack 记录被清,后续包就可能被标记为
INVALID 进而被 DROP。
- IP 分片:UDP 包大于 MTU 会被分片,传一堆片,丢任何一个,整个 UDP 包就废了。要么调 MTU,要么应用层自己控制包大小。
# 调大 UDP 相关缓冲区
sysctl -w net.core.rmem_max=16777216
sysctl -w net.ipv4.udp_rmem_min=8192
sysctl -w net.ipv4.udp_mem="8192 131072 16777216"
注意:net.ipv4.udp_mem 是全局内存限制,单位是页(4KB)。三个值分别是 min、pressure、max。超过 pressure 内核就开始有压力了,超过 max 就直接丢包。
场景 2:TCP 丢包
TCP 丢包的典型表现就是重传。发送方没收到 ACK,就会触发超时重传、快速重传或 SACK 重传。
# 看 TCP 重传统计
nstat -az | grep -E "(TcpRetrans|TcpOutRsts|TcpInErrs)"
# 盯着每个连接的重传次数
ss -tan -i | grep -E "retrans|lost"
TCP 丢包的常见根因:
- 网络质量差:RTT 剧烈抖动、带宽被塞满。可以用
sar -n DEV 盯着网卡流量。
- socket buffer 满了:发不出去或收不下来。
ss -tan -i 可以看队列占用。
- 对端窗口太小:对端应用层不读数据,
cwnd 被缩到很小。
- 中间网络设备丢包:防火墙、路由器、交换机端口丢包。
- NAT 会话超时:SNAT 设备的会话表先超时了,再进来的包就丢了。
场景 3:跨机房丢包
跨机房链路,天生高延迟、中间设备多,丢包风险更高。
# mtr 跨机房,可以发更多包、更短间隔来观察
mtr -r -c 200 -i 0.5 <目标IP>
# 长时间持续性观察
mtr -r -c 1000 -i 1 <目标IP>
针对性的优化手段:
- 启用 TCP BBR 拥塞控制算法,代替传统的 CUBIC,在高丢包场景下优势明显。
- 调大 TCP socket buffer。
- 业务上考虑用更高效的序列化/协议。
- 长肥管道开启 TCP Fast Open、TCP_NODELAY 等特性。
场景 4:K8s 容器网络丢包
K8s 网络模型的复杂性为丢包排查带来了新难度。
# 在宿主机上查看 Pod 对应的 veth pair 和网桥
ip -s link show veth<xxxx>
ip -s link show cni0
ip -s link show docker0
# 检查 conntrack,容器化场景下连接数爆炸是常态
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
K8s 网络丢包的常见症结:
- MTU 不一致:VXLAN、IPIP 等 Overlay 封装会吃掉一部分 MTU,如果容器内还是 1500,大包就会被分片或丢弃。容器内 MTU 一般要调小到 1450 或 1400。
- conntrack 表被打满:POD 数量多,连接跟踪表消耗巨大。
- iptables 规则性能瓶颈:kube-proxy 在 iptables 模式下,大量 Service 规则匹配会非常缓慢,直接导致丢包。可以考虑换成 IPVS 模式。
- CNI 插件自身的 bug 或性能问题。
场景 5:DNS 解析丢包
这也算一类特殊的“丢包”,因为解析慢或失败,同样造成服务不可用。
# 用 dig 跟踪完整的 DNS 解析过程
dig +trace example.com
# 指定权威或公共 DNS 服务器进行解析测试
dig @8.8.8.8 example.com
# DNS 默认走 UDP,但失败时可以强制走 TCP 试试
dig +tcp example.com
# 直接在服务器上抓 DNS 流量
tcpdump -i eth0 -nn -s 0 port 53
DNS 解析丢包的常见问题:
- 防火墙拦截 UDP 53 端口。
- DNS 服务器对 UDP 请求做了限速,切到 TCP 模式解决。
- DNS 服务器本身不可达。
- 本机 DNS 缓存机制(nscd、systemd-resolved)出现异常。
高级话题
1. TCP BBR 拥塞控制算法
BBR(Bottleneck Bandwidth and Round-trip propagation time)是 Google 提出的拥塞控制算法,它不再像传统 CUBIC 那样,一看到丢包就认为拥塞并主动降速,而是基于网络的实时带宽和 RTT 来决策,因此在有一定丢包率的链路上能极大地提高吞吐。
# 看当前内核支持哪些拥塞控制算法
sysctl net.ipv4.tcp_available_congestion_control
# 启用 BBR(需要 Linux 4.9+ 内核)
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.core.default_qdisc=fq
# 验证是否生效
sysctl net.ipv4.tcp_congestion_control
# 输出:net.ipv4.tcp_congestion_control = bbr
BBR 在跨机房、大带宽、高 RTT 且有微量丢包的场景下,效果极其明显。但在低延迟局域网里提升有限。
再次提醒:CentOS 7 默认 3.10 内核无法直接启用 BBR,需要升级内核。
2. TCP 缓冲区自动调优
Linux 的 tcp_rmem 和 tcp_wmem 定义了 TCP socket 的 buffer 值,是三个一组的数字。
# 查看当前缓冲区的 min, default, max 值
sysctl net.ipv4.tcp_rmem
sysctl net.ipv4.tcp_wmem
# 输出类似:
# net.ipv4.tcp_rmem = 4096 87380 6291456
# net.ipv4.tcp_wmem = 4096 65536 4194304
tcp_moderate_rcvbuf = 1:这个开关默认开,允许内核在 min 和 max 之间动态调整接收缓冲区,非常智能。
现代网卡多是多队列网卡,能把不同的网络流散列到不同队列,每个队列的中断可以绑定到不同 CPU 核上处理,这样多核 CPU 就能并行处理网络包。
# 查看网卡的多队列设置
ethtool -l <网卡名>
# 查看中断号及其亲和性
cat /proc/interrupts | grep <网卡名>
# 手动调整某个中断的 CPU 亲和性(一般用 irqbalance 服务自动处理)
# echo <CPU mask> > /proc/irq/<IRQ号>/smp_affinity
生产环境建议打开 irqbalance 服务,让它自动去分配。
4. 时间戳和延迟
网络延迟由几部分组成:串行化延迟(包大小/带宽)、传播延迟(距离/光速,约5μs/km)、处理延迟(设备处理包的时间)、队列延迟(在 buffer 里等待的时间)。排查延迟,得先心里有数,知道是哪种延迟在作怪。ping 的 RTT 是所有延迟的结合体,而 mtr 能帮我们看到路径上每一跳的延迟增量。
5. MTU 和分片
默认 MTU 是 1500 字节。但一旦涉及隧道(VXLAN、GRE、IPsec)或 PPPoE 拨号,MTU 就会变小。两端 MTU 不一致,大包就会被分片,增加了丢包风险和 CPU 开销。
# 探测到目标 IP 的路径 MTU
tracepath <目标IP>
# 用大包 ping 测试,不允许分片
ping -M do -s 1472 <目标IP>
# -M do 设置 DF(Don't Fragment)标志,-s 1472 + 8(ICMP头) + 20(IP头) = 1500
大包 ping 不通,但小包通,就说明路径上某个设备的 MTU 小于 1500。
不同业务场景的排查思路
Web 服务(HTTP / HTTPS)
- Nginx 的 error 日志是发现连接问题的第一手资料。
- 用
ss 观察连接状态(尤其是 TIME_WAIT 是否堆积)。
- 抓包分析 TCP 握手、数据传输和异常的 RST 包。
- 压测下观察 P99 延迟变化。
数据库(MySQL / Redis)
- 检查数据库的慢查询日志。
ss 查看数据库服务端口的连接数是否异常。
- 抓包分析 MySQL 等协议交互流程,看有没有异常的断开或重连。
- 主从同步延时,分析 binlog dump 线程的传输过程。
微服务(RPC)
- 查看 RPC 框架(如 gRPC、Dubbo)的超时和重试日志。
ss 观察服务间大量长连接的状态。
- 抓包看 RPC 协议交互,确认是调用方没发,还是服务方没回。
- 检查长连接的 keepalive 机制是否生效。
视频流(RTSP / RTMP / WebRTC)
- 流媒体服务端自带的丢包统计和日志。
- 抓包检查 RTP 包的 Sequence Number 是否连续。
- 分析 RTCP 协议里的 NACK(丢包重传请求)反馈。
- 算清总带宽(视频码率 × 并发用户数)是否已经顶满。
监控告警建议
丢包问题不能靠”人肉“去发现,必须有持续的监控。
1. 网卡丢包
- alert: NicRxDropped
expr: rate(node_network_receive_dropped_total[5m]) > 100
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} 网卡 {{ $labels.device }} 接收丢包率过高"
description: "丢包速率 {{ $value }} 个/秒"
- alert: NicTxDropped
expr: rate(node_network_transmit_dropped_total[5m]) > 100
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} 网卡 {{ $labels.device }} 发送丢包率过高"
2. TCP 重传
- alert: TcpRetransHigh
expr: rate(node_netstat_Tcp_RetransSegs[5m]) / rate(node_netstat_Tcp_OutSegs[5m]) > 0.05
for: 10m
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} TCP 重传率过高"
description: "重传率 {{ $value | humanizePercentage }},正常应该 < 5%"
3. 连接跟踪表
- alert: ConntrackTableFull
expr: node_nf_conntrack_entries / node_nf_conntrack_entries_limit > 0.8
for: 5m
labels:
severity: critical
annotations:
summary: "{{ $labels.instance }} 连接跟踪表使用率超过 80%"
description: "已用 {{ $value | humanizePercentage }}"
4. 软中断队列
- alert: SoftnetDropped
expr: rate(node_softnet_dropped_total[5m]) > 0
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} softnet 队列存在丢包"
排查记录模板
每次大排查结束,强烈建议沉淀一份标准化文档,这对后续的复盘和新人是无价之宝。
## 丢包问题排查记录
### 基本信息
- 发生时间:YYYY-MM-DD HH:MM
- 影响范围:(业务 / 服务 / 集群 / 链路)
- 故障时长:(持续多久)
- 业务影响:(QPS 下降、用户投诉)
### 现象
(记录业务反馈、监控告警截图、异常日志)
### 排查路径
1. (第一步做了啥,看到啥结果)
2. (第二步……)
3. (逐步推进,直到定位根因)
### 根因
(最终确定的根因,比如“机房A到B的专线 OSPF 路由震荡导致 TCP 重传率飙升”)
### 修复方案
(临时止血方案 + 长期优化方案)
### 验证
(修复后如何验证的,附上前后对比的监控图表)
### 预防措施
(下次怎么避免,比如加监控、加限流、调整架构)
### 复盘
(本次排查的经验教训总结)
排查工具速查
命令速查
# 网络层
ping -c 100 -i 0.2 <IP> # 基础 ping
traceroute -n <IP> # 路由跟踪
mtr -r -c 100 <IP> # 持续 ping + 路由
mtr --tcp --port 443 -r -c 100 <IP> # TCP 模式 mtr
tracepath <IP> # 路径 MTU 发现
# 协议栈统计
nstat -az # 实时协议栈统计
cat /proc/net/snmp # 协议栈累计统计
cat /proc/net/netstat # 详细协议栈统计
# socket 状态
ss -tan # TCP 连接
ss -uan # UDP socket
ss -s # socket 统计
ss -tan -i -e # 详细 socket 信息
# 网卡统计
ip -s link show <网卡> # 网卡收发统计
ethtool <网卡> # 网卡协商信息
ethtool -S <网卡> # 网卡驱动级统计
ethtool -g <网卡> # ring buffer 大小
ethtool -l <网卡> # 多队列信息
ethtool -k <网卡> # 网卡特性
# 连接跟踪
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
cat /proc/net/nf_conntrack
conntrack -L # 列出所有 conntrack(需安装 conntrack-tools)
conntrack -S # conntrack 统计
# 内核日志
dmesg | grep -i "network"
dmesg | grep -i "conntrack"
dmesg | grep -i "drop"
dmesg | grep -i "tcp"
# 抓包
tcpdump -i eth0 -nn -s 0 -w file.pcap # 抓包存盘
tcpdump -i eth0 -nn 'tcp port 80' # 抓 HTTP 流量
tcpdump -i eth0 -nn -X 'port 53' # 抓 DNS 流量
# 系统级丢包点
cat /proc/net/softnet_stat # 软中断统计
dropwatch -l kas # 内核丢包点
perf record -e skb:kfree_skb -a -g # perf 跟踪 skb 释放
关键文件
/proc/net/snmp # IP / TCP / UDP / ICMP 统计
/proc/net/netstat # 详细协议栈统计
/proc/net/softnet_stat # 软中断统计
/proc/net/nf_conntrack # 连接跟踪表内容
/proc/net/stat/nf_conntrack # 连接跟踪统计
/proc/sys/net/ipv4/tcp_rmem # TCP 接收缓冲区
/proc/sys/net/ipv4/tcp_wmem # TCP 发送缓冲区
/proc/sys/net/core/somaxconn # accept 队列上限
/proc/sys/net/ipv4/tcp_max_syn_backlog # SYN 队列上限
/proc/sys/net/ipv4/ip_local_port_range # 临时端口范围
/proc/sys/net/netfilter/nf_conntrack_max # conntrack 表上限
/proc/sys/fs/file-nr # 系统 fd 统计
/proc/sys/fs/file-max # 系统 fd 上限
关键内核参数
# 网络层
net.core.somaxconn = 65535 # accept 队列
net.core.netdev_max_backlog = 65535 # 网卡接收队列
net.ipv4.tcp_max_syn_backlog = 65535 # SYN 队列
net.ipv4.ip_local_port_range = 1024 65535 # 临时端口范围
# TCP 参数
net.ipv4.tcp_timestamps = 1 # 时间戳(必须启用才能用 tw_reuse)
net.ipv4.tcp_tw_reuse = 1 # 复用 TIME_WAIT
net.ipv4.tcp_fin_timeout = 15 # FIN_WAIT_2 超时
net.ipv4.tcp_slow_start_after_idle = 0 # 关闭空闲后慢启动
net.ipv4.tcp_fastopen = 3 # TCP Fast Open
net.ipv4.tcp_rmem = 4096 87380 16777216 # TCP 接收 buffer
net.ipv4.tcp_wmem = 4096 65536 16777216 # TCP 发送 buffer
# 拥塞控制
net.ipv4.tcp_congestion_control = bbr # 启用 BBR
net.core.default_qdisc = fq # FQ 队列规则
# 连接跟踪
net.netfilter.nf_conntrack_max = 1048576 # conntrack 表上限
不同角色的排查视角
应用运维视角
最关注“业务可用性”。除了业务监控和日志,很少直接动手抓包,更多依赖于系统和网络层的同事。
系统运维视角
关注“机器和网络健康”,是连接应用和网络的桥梁。从机器负载、网络统计、链路质量到抓包分析,都得熟练,是排查丢包问题的核心主力。
网络运维视角
关注“网络设备健康”。会去查交换机路由器端口统计、光模块光衰、路由/ARP/MAC表等。
三个角色的协作
清晰的协作模式是:应用反馈现象 -> 系统层初步定位 -> 网络层设备确认 -> 修复后应用验证。关键要做好信息同步,带着现象、排查动作和初步判断去沟通。
常见问题 FAQ
Q1:ping 没丢包但业务超时,是怎么回事?
ping 走 ICMP,业务走 TCP,两者被网络设备区别对待是常有的事,比如路由器对 ICMP 限速。也可能是 TCP 状态机异常或应用层自己处理得慢。快切到 mtr --tcp --port 443 或者直接抓包吧。
Q2:丢包率 1% 严重吗?
看场景。实时音视频 1% 就花屏卡顿;HTTP 短连接几乎无感;数据库长连接可能引发同步延时;金融交易场景下就绝对是灾难。
Q3:服务器上看到的丢包和对端看到的丢包不一致,怎么回事?
可能是抓包位置不同(你抓发出,他抓收到),中间设备有丢包,或者存在 buffer 暂存。想准确定位,就得在链路多个关键位置同时抓包对比。
Q4:iPerf 测速正常但业务慢,是怎么回事?
iperf 只是测出了链路极限 TCP 吞吐,不代表业务体验。应用层处理慢、协议交互来回多、TLS 握手慢、小包多等等,都会导致业务感觉慢。
Q5:怎么判断丢包是物理层还是网络层?
- 物理层:看网卡
ethtool -S,rx_fifo_errors、rx_crc_errors 等有计数。
- 网络层:
ip -s link 硬件统计无异常,但 /proc/net/snmp 的 InDiscards 或 InHdrErrors 在增加。
- 应用层:前端都没丢,但
ss 看到连接的 RST 或 retrans 在增加。
总结
网络丢包排查,耐心和方法缺一不可。自顶向下逐层排查,每一层都有对应的王道工具和关键指标。
核心工具链就这六板斧:ping/mtr -> ss/netstat -> nstat//proc -> ip/ethtool -> tcpdump/Wireshark -> dmesg/dropwatch。
核心指标盯死这五个:TCP 重传率、网卡 dropped 计数、conntrack 使勇率、softnet 丢包数、mtr 丢包率变化。
排查的基本功就是:看业务现象 -> 查协议栈统计 -> 查网卡计数 -> mtr 定界 -> 抓包实锤 -> 调参或修链路。
每次搞完大的丢包事故,一定把整个过程写成排查记录模板里的那种标准化文档,这是团队最宝贵的财富。云栈社区上也有很多同行分享的真实排查案例,没事了多去逛逛,能少走不少弯路。