Linux 内核技术实战课 · TCP 重传模块:把“看不见的丢包“揪出来 Linux 内核技术实战课 · TCP 重传模块把看不见的丢包揪出来实验环境说明本文所有数据全部来自华为云 FlexusXx2e.8u.16g双机真实实验——靶机 ecs-665a-0003私网 192.168.0.198对端 ecs-665a-0001私网 192.168.0.13均为 Ubuntu 24.04.4 LTS、内核6.8.0-106-generic弱网使用tc netem在两端eth0上真实注入延迟 随机丢包非 loopback 自环。文中每一个数字均来自真实命令输出未做任何编造。一、基础篇 · 如何观测 TCP 重传TCP 重传是网络中最常见也最容易被误解的现象。很多朋友一上来就tcpdump -i eth0 tcp-retransmit——抱歉TCP 没有独立的重传标志位不存在这样的过滤器。那到底怎么查1.1 排查四件套实战中排查 TCP 重传我依赖以下四板斧按干扰从小到大排列①nstat -az TcpRetransSeg—— 内核实时重传段计数最轻量nstat直接读取内核 SNMP 计数器不碰网卡、不开抓包零开销。实验 5 中我的操作流程# 1. 记录基线$ nstat-azTcpRetransSegs#TcpRetransSegs 18697 0.0# 2. 跑流iperf3 8s注入 2% 丢包# 3. 跑流后再次读取$ nstat-azTcpRetransSegs#TcpRetransSegs 33577 0.0# 增量33577 - 18697 14880段结论增量 14880 段 8 秒内因 2% 丢包触发的重传段数。这是最权威的计数无需任何猜测。②/proc/net/snmp的TcpRetransSegs—— 文本化累计计数与nstat同源只是以文本形式呈现$ grep ^Tcp: /proc/net/snmp Tcp: 1 200 120000 -1 138 9 0 22 3 23577 498890 6019 0 230 0RetransSegs 6019实验 3 跑完 cubicbbr 后累计值与同期的nstat输出完全一致。经验nstat和/proc/net/snmp读取的是同一组内核计数器用哪个都行——但关键是看增量不是看绝对值。重启过的机器初始值接近 0生产上跑了几周可能几百万你只需要跑流前后的差值。③ss -ti—— 单 socket 级重传、RTO、cwnd 全景nstat是全局计数如果你想知道具体哪个连接在重传、重传了多少用ss -ti$ ss-tin|grep-A35201cubic wscale:7,7 rto:201 rtt:0.204/0.136 mss:1448 pmtu:1500 rcvmss:536 advmss:1448 cwnd:10 ssthresh:8 bytes_sent:118198829 bytes_retrans:2306664 bytes_acked:115877686 segs_out:81633 segs_in:9631 data_segs_out:81631 send 568Mbps lastrcv:1003 pacing_rate 679Mbps delivery_rate 858Mbps delivered:80029 busy:1002ms unacked:10 retrans:0/1593 rcv_space:14480 rcv_ssthresh:64088 notsent:932512 minrtt:0.056 snd_wnd:2154496关键指标解读字段实验5实测值含义rto:201201 ms当前超时重传定时器值动态退避rtt:0.204/0.1360.204 ms avg / 0.136 ms mdev平滑 RTT 与抖动cwnd:1010 段拥塞窗口已塌缩bytes_retrans:2306664约 2.3 MB该连接累计重传字节数retrans:0/15930 超时 / 1593 段快速重传核心指标——后排详讲delivery_rate:858Mbps858 Mbps实际投递速率④tcpdump—— 抓包人工分析最后手段前面三板斧能回答有没有重传、多少重传但回答不了这些重传的包长什么样。这时才需要抓包# 实验58 秒抓包落盘约 857 MB$ tcpdump-ieth0-s94-w/tmp/cap.pcap $ capinfos /tmp/cap.pcap# 文件大小: 857,666,096 bytes# 包含包数: 187,668抓包后用 Wireshark 打开重传包的特征是同一 Seq 号的报文出现两次以上且后发的包时间晚于前一个。Wireshark 靠[TCP Retransmission]注解帮你标出来但这只是后处理启发式判定网卡/内核本身并不会给包打我是重传的标签。二、基础篇 · 重传是怎么发生的理解了怎么观测再看重传的两种触发机制。2.1 超时重传RTO发送端发出一个数据段启动 RTO 定时器。如果 RTO 超时仍未收到 ACK则重传该段。RTO 初始值200 msRtoMin~120 sRtoMax每次超时后 RTO 指数退避直到 120 s这就是业务上连接卡死几秒钟的根源ss -ti中的rto:201就是当前连接的定时器值retrans:0/1593前半部分0就是超时重传次数为 0——说明实验 5 中所有丢包都没有等到超时这就引出了第二种。2.2 快速重传Fast Retransmit接收端收到乱序报文序列号不连续时会立即回复重复 ACKDupACK或携带 SACK 选项告知发送端缺失哪些段。发送端收到3 个重复 ACK标准阈值后不等 RTO 超时立即重传缺失的段。实验 5 的nstat -az拆解就把这个讲透了TcpRetransSegs 39774 # 总重传段数 TcpExtTCPFastRetrans 39540 # 快速重传段数 (99.4%!) TcpExtTCPSlowStartRetrans 117 # 超时后慢启动重传段数 (0.3%) TcpExtTCPLostRetransmit 840 # 重传后仍未送达最终判丢 TcpExtTCPSynRetrans 47 # SYN 重传 TcpExtTCPRetransFail 0 # 重传失败的段数0TCPFastRetrans(39540) ≫ TCPSlowStartRetrans(117)占比 99.4%——这就是我们说的健康的重传。绝大多数丢包被快速重传以毫秒级代价修复了业务几乎无感知。只有TCPSlowStartRetrans发生时才意味着 RTO 超时、cwnd 塌缩、业务秒级卡顿。2.3 弱网如何把正常报文变成重传弱网的本质是两个效应叠加延迟 丢包。延迟delay报文到达慢 → ACK 回来慢 → 发送端发送空窗期增大 → 吞吐下降丢包loss报文丢了 → ACK 缺失 → 触发 dupACK 或 RTO我们在实验 3 两端各注入delay 30ms loss 1%RTT ≈ 60ms双向 1% 随机丢包cubic 吞吐从内网无注入时的线速降到18.51 Mbps不是因为带宽不够而是丢包导致 cwnd 反复塌缩链路一直被缓慢恢复填满从未达到高水位。三、案例篇 · 弱网下 cubic vs bbr 谁更稳3.1 实验设计同一组弱网参数两端 eth0 各delay 30ms loss 1%同一台 iperf3 服务端先跑 cubic 再切 bbr对比差异。3.2 真实数据对比指标cubicbbr倍数发送吞吐18.51 Mbps268.99 Mbps≈14.5×接收吞吐17.04 Mbps267.87 Mbps≈15.7×iperf 报告重传1125 段4789 段bbr 更多nstat 总重传 (含多流累计)6019同口径—3.3 为什么 bbr 碾压 cubic核心原因cubic 是丢包敏感型算法一旦检测到丢包就塌缩 cwnd然后从更小的窗口慢慢恢复——在 1% 随机丢包下窗口一直在塌缩-恢复-塌缩中循环链路利用率极低。BBR 基于带宽和 RTT 建模不把丢包当作拥塞信号它用 pacing 填满管道丢包靠快速重传弥补。3.4 生产上如何切换 bbr重点踩坑Ubuntu 24.04 内核默认只在tcp_available_congestion_control中提供reno cubic没有 bbr。# 错误示范直接切换会报错$sysctl-wnet.ipv4.tcp_congestion_controlbbr# sysctl: setting key net.ipv4.tcp_congestion_control: No such file or directory正确姿势# 1. 加载 bbr 内核模块$ modprobe tcp_bbr# 2. 确认已加载$sysctlnet.ipv4.tcp_available_congestion_control# net.ipv4.tcp_available_congestion_control reno cubic bbr# 3. 切换$sysctl-wnet.ipv4.tcp_congestion_controlbbr# 4. 可选写入 /etc/sysctl.conf 持久化# echo net.ipv4.tcp_congestion_control bbr /etc/sysctl.conf经验modprobe tcp_bbr仅当前会话有效重启后需要重新加载。如需开机自启建议写入/etc/modules或/etc/modules-load.d/。3.5 切换 bbr 后的预期管理从实验 3 的数据可以清晰看到bbr 的 iperf 报告重传数4789比 cubic1125高得多。这不叫bbr 不稳这叫bbr 用更多的重传换取了 14.5 倍的吞吐。在 BBR 的模型里丢包不是拥塞——你的运维监控如果只看TcpRetransSegs报警切 bbr 后重传阈值需要重新校准。生产建议长肥管道 / 跨公网 / 无线链路 → 优先 BBR低延迟内网 / CPU 受限场景 → 可以继续用 cubic切 bbr 后重传计数器预期升高阈值建议调整 3-5 倍四、案例篇 · RTT 抖动如何定位用户说慢是最难排查的问题之一。从 TCP 层面看RTT 是最直接的诊断维度。4.1 先打 RTT 基线$ping192.168.0.13-c5# --- 192.168.0.13 ping statistics ---# 5 packets transmitted, 5 received, 0% packet loss, time 4126ms# rtt min/avg/max/mdev 0.157/0.189/0.239/0.032 ms同 VPC 内网基线 RTT 仅为0.189 ms亚毫秒级。4.2 注入延迟看变化本机 eth0 注入 50 ms 延迟$ tc qdiscadddev eth0 root netem delay 50ms# 再 ping$ping192.168.0.13-c5# --- 192.168.0.13 ping statistics ---# rtt min/avg/max/mdev 50.159/50.187/50.232/0.031 msRTT 从 0.189 ms 精确变为50.187 ms差值 50 ms 注入延迟。这一步就验证了延迟确确实实来自本机 egress 路径。4.3 ss -tin 验证连接级 RTT$ ss-tin|grep-A35201cubic wscale:7,7 rto:251 rtt:50.187/0.032 mss:1448 pmtu:1500 rcvmss:536 advmss:1448 cwnd:1977 ssthresh:1889 bytes_sent:68724277 bytes_retrans:915880 bytes_acked:64945726 segs_out:47473 segs_in:1308 data_segs_out:47471 send 456324706bps lastsnd:11 lastrcv:1801 lastack:11 pacing_rate 547586912bps delivery_rate 455343000bps delivered:44877 app_limited busy:1800ms rwnd_limited:207ms(11.5%)sndbuf_limited:101ms(5.6%)unacked:1977 retrans:0/633 dsack_dups:15 reordering:28 reord_seen:2 rcv_space:14480 rcv_ssthresh:64088 notsent:1319128 minrtt:50 snd_wnd:3954176rtt:50.187/0.032→ 与 ping 结果一致确认是本机侧延迟pmtu:1500/mss:1448→ 链路 MTU 无问题delivery_rate:455 Mbps→ 即使有 50ms 延迟大窗口cwnd1977仍能填满retrans:0/633→ 0 超时重传633 段快速重传bytes_retrans:915880→ 该连接累计重传 ~916 KB4.4 关联 sysctl连接建立与保活实验 1 中记录了与连接生命周期相关的 sysctlnet.ipv4.tcp_syncookies1# SYN Cookie 防 SYN Flood生产保持开启net.ipv4.tcp_tw_reuse2# 允许复用 TIME-WAIT 连接设为2仅安全场景net.ipv4.tcp_fin_timeout60# FIN-WAIT-2 超时 60snet.ipv4.tcp_max_syn_backlog1024# SYN 半连接队列上限net.core.somaxconn4096# 全连接 accept 队列上限sshd 监听 :22 的 Send-Q4096net.ipv4.tcp_abort_on_overflow0# 全连接队列满时不暴力 RST默认优雅丢弃4.5 关联 sysctl收发缓冲区与协议特性实验 2 中记录的缓冲区参数net.ipv4.tcp_rmem40961310726291456# 读缓冲区 min-default-max字节net.ipv4.tcp_wmem4096163844194304# 写缓冲区 min-default-max字节net.ipv4.tcp_mem179415239221358830# TCP 全局内存单位页4KiB# 179415×4K ≈ 734 MiBmin# 239221×4K ≈ 935 MiBpressure 阈值# 358830×4K ≈ 1.47 GiBmaxnet.ipv4.tcp_sack1# 选择性 ACK开启加速丢包判断net.ipv4.tcp_window_scaling1# 窗口缩放开启长肥管道必备net.ipv4.tcp_timestamps1# 时间戳选项开启精确 RTT 计算尤其是tcp_sack1与tcp_window_scaling1它们是快速重传高效工作的基础设施——没有 SACK发送端只能收到累积 ACK不知道丢了哪几个段没有 Window Scaling窗口最大 65535 字节在 50ms RTT 下带宽瓶颈只有 ~10 Mbps。4.6 路由与 MTU 验证# 确认流量走哪个网卡$iproute get192.168.0.13# 192.168.0.13 dev eth0 src 192.168.0.198 uid 0# cache# 验证链路 MTU1472(DATA) 28(ICMPIP头) 1500DF不分片$ping-Mdo-s1472192.168.0.13-c3# 1480 bytes from 192.168.0.13: icmp_seq1 ttl64 time50.2 ms# 3 packets transmitted, 3 received, 0% packet loss1500 字节DF探测成功链路 MTU ≥ 1500无需 PMTU 绕行。五、分析篇 · 一步一步分析真实重传最后我把实验 5 的完整排查链路整理为一份TCP 重传排查清单照着做就能复现。5.1 复现步骤Step 1记录重传计数基线$ nstat-azTcpRetransSegs# TcpRetransSegs 18697$grep^Tcp:/proc/net/snmp|awk{print RetransSegs:,$NF}# RetransSegs: 18697两个数据源同源交叉确认。Step 2注入弱网模拟真实链路丢包$ tc qdiscadddev eth0 root netem loss2%Step 3起 iperf3 流并同时抓包服务端$ iperf3-s-D客户端对端 192.168.0.13$ iperf3-c192.168.0.198-t8本机抓包$ tcpdump-ieth0-s94-w/tmp/cap.pcapsleep9;kill%1Step 4跑流后读数$ nstat-azTcpRetransSegs# TcpRetransSegs 33577# 增量 33577 - 18697 14880 段8秒内净增Step 5ss -ti 看哪个连接在重传$ ss-tin|grep-A35201# retrans:0/1593 → 0 超时1593 段快速重传# bytes_retrans:2306664 → 2.3 MB 累计重传# rto:201 → RTO 定时器 201msStep 6nstat -az 全量拆解重传构成$ nstat-az# TcpRetransSegs 39774# TcpExtTCPFastRetrans 39540 # 99.4%# TcpExtTCPSlowStartRetrans 117 # 0.3%# TcpExtTCPLostRetransmit 840 # 重传后仍丢# TcpExtTCPSynRetrans 47 # SYN 重传# TcpExtTCPRetransFail 0 # 全部重传成功Step 7清理$ tc qdisc del dev eth0 root $pkill-xiperf35.2 快速判断矩阵观测现象可能的根因确认手段TcpRetransSegs持续增长链路存在丢包ss -ti看单连接 retransTCPSlowStartRetrans占比高RTO 超时频繁链路极度拥塞看 cwnd 是否持续 10TCPFastRetrans占比高吞吐正常少量随机丢包系统健康无需处理这是正常工作方式bytes_retrans大但retrans:0/0重传历史数据当前无丢包对比前后两次观察rtt突然增大如 0.2ms → 50ms链路路径延迟增加ping验证 ip route getrto接近 RtoMax120s连接即将超时断开检查对端是否存活TcpExtTCPLostRetransmit增长快重传仍丢链路质量极差需要网络团队配合六、本模块要点速记TCP 重传没有独立标志位不能用单条 tcpdump 过滤器直接抓重传包排查四件套nstat内核计数→/proc/net/snmp文本计数→ss -ti单连接→tcpdump抓包后处理看增量别看绝对值——TcpRetransSegs跑流前后差值才是权威数字大部分重传是健康的——TCPFastRetrans ≫ TCPSlowStartRetrans是正常状态BBR 在丢包弱网下吞吐 ≈ cubic 的 14.5 倍但重传计数也会更高这是预期行为切 BBR 必须先modprobe tcp_bbrUbuntu 24.04 内核默认没有加载ss -tin的rtt字段直接读出连接级 RTT 抖动是定位慢的第一工具SACK / Window Scaling / Timestamps是快速重传高效工作的基础设施不要关七、下篇预告重传问题搞定后另一类让 SRE 头疼的问题浮出水面——“CPU 利用率没有明显异常但应用就是慢”。下一节我们将进入内核态 CPU 利用率飙高模块从perf top、/proc/softirqs到netstat -s软中断计数手把手排查ksoftirqd 跑满、NET_RX 软中断分发不均衡、napi_poll 次数暴涨、单核 softirq 飙到 80% 的典型案例。敬请期待。