root@node3:~# ansible all -m shell -a "nstat -az | grep -Ei 'TcpAbortOnTimeout|TcpTimeouts|TcpRetransFail'"
node5 | CHANGED | rc=0 >>
TcpExtTCPTimeouts 1188892 0.0
TcpExtTCPAbortOnTimeout 290782 0.0
TcpExtTCPRetransFail 325 0.0
node3 | CHANGED | rc=0 >>
TcpExtTCPTimeouts 788233 0.0
TcpExtTCPAbortOnTimeout 253865 0.0
TcpExtTCPRetransFail 39 0.0
node6 | CHANGED | rc=0 >>
TcpExtTCPTimeouts 89526 0.0
TcpExtTCPAbortOnTimeout 24005 0.0
TcpExtTCPRetransFail 34 0.0
node4 | CHANGED | rc=0 >>
TcpExtTCPTimeouts 912459 0.0
TcpExtTCPAbortOnTimeout 275961 0.0
TcpExtTCPRetransFail 82454 0.0
Compacted conversation## 分析:事故 24h 的重传倍率
关键指标是 TcpRetransFail(重传最终失败 = 底层发不出去),node4 是唯一的异常节点:
原始数据归一化
| 节点 | Uptime (天) | TcpRetransFail | RetransFail/天 | TcpTimeouts/天 | AbortOnTimeout/天 |
|---|---|---|---|---|---|
| node4 | 62 | 82,454 | 1,330 | 14,717 | 4,451 |
| node5 | 62 | 325 | 5.2 | 19,176 | 4,690 |
| node3 | 55 | 39 | 0.7 | 14,331 | 4,612 |
| node6 | 60 | 34 | 0.6 | 1,492 | 400 |
TcpRetransFail — 事故指纹
node4 的 TcpRetransFail 是其他节点的 ~620 倍(82,454 vs 平均 133)。
将异常量归因到 24h 事故窗口:
正常基线(peer 均值): (5.2 + 0.7 + 0.6) / 3 ≈ 2.2 次/天
node4 正常 62 天预期: 2.2 × 62 ≈ 136 次
事故产生的异常量: 82,454 - 136 ≈ 82,318 次(24h 内)
正常 24h 速率: 2.2 次/天
事故 24h 速率: 82,318 次/天
倍率:82,318 / 2.2 ≈ 37,000 倍
结论:事故期间 TcpRetransFail 速率是正常的 ~3.7 万倍
为什么只有 TcpRetransFail 爆炸?
| 指标 | 含义 | node4 是否异常 | 原因 |
|---|---|---|---|
| TcpRetransFail | tcp_retransmit_skb() 底层发送失败 | 是,3.7万倍 | TSO/GSO off → 段数爆炸 → 重传队列溢出 → 底层无法发出 |
| TcpTimeouts | 单次 RTO 超时触发 | 否(与 node3/node5 相当) | peer 节点也在等 node4 的回包,双向超时 |
| TcpAbortOnTimeout | 连接因超时被 abort | 否(与 node3/node5 相当) | 同上,peer 侧也在 abort 连接 |
注意 node5/node3 的 TcpTimeouts 和 AbortOnTimeout 也很高 — 它们是 node4 连接震荡的 受害者(peer 侧超时),而 node6 数值低说明它与 node4 的交互较少。
TcpRetransFail 是 TSO/GSO off 的独特指纹:只有发送端(node4)会因段数爆炸导致 tcp_retransmit_skb() 在底层失败,接收端不会。
这不奇怪,这恰好是 TSO/GSO off 的病理特征:
丢包低 + 重传炸 = 本地 TCP 栈溢出,不是线路问题
正常路径(TSO on):
app write 64KB → NIC 硬件分段 → 1 个 GSO 段 → wire → 正常
故障路径(TSO off):
app write 64KB → 内核软分段 → 44 个 1460B 段 → wire → 正常(不丢包!)
↓
但 TCP 需要逐段跟踪/ACK/重传
sk_wmem_alloc 爆炸
↓
RTO 触发 tcp_retransmit_skb()
→ sk_wmem_alloc > sk_sndbuf
→ return -ENOMEM
→ TcpRetransFail++
核心机制:tcp_retransmit_skb() 在尝试重传时检查 socket 写缓冲区:
// net/ipv4/tcp_output.c
if (atomic_read(&sk->sk_wmem_alloc) > sk->sk_sndbuf) {
TCP_INC_STATS(TCP_MIB_RETRANSFAIL); // ← 这就是 82,454 的来源
return -ENOMEM;
}
段数 44 倍 → 每个段占 sk_buff + skb_shared_info(~240B 开销)→ wmem 消耗暴涨 → 重传根本发不出去。
所以数据完全自洽
| 现象 | 原因 |
|---|---|
| 丢包低 | 线路/交换机正常,包确实发出去了 |
| TcpTimeouts node4 不异常 | RTO 超时次数正常,peer 侧也有相同量级 |
| TcpRetransFail node4 爆炸 3.7万倍 | 只有发送端才会 wmem 耗尽,重传发不出 |
| peer 节点 RetransFail 正常 | peer 的 TSO 没关,段数正常,wmem 够用 |
这就是为什么 TcpRetransFail 是 TSO/GSO off 的独特指纹 — 它不反映线路质量,而反映发送端 TCP 栈在段数爆炸下的本地资源耗尽。连接不是被"丢包"杀死的,是被"重传发不出"饿死的。
只有发送端才会 wmem 耗尽,重传发不出发送不是 tx 么? 为什么 rx 丢包?
好问题。这里有两个独立的症状链,不是因果关系:
为什么 tx 问题会出现 rx 丢包
根因:ethtool -K bond0 tx off(node4)
│
├─→ 症状链 1(tx 侧):段数 44x → sk_wmem 耗尽 → TcpRetransFail 3.7万倍
│ ↑ 这不是"丢包",是本地 socket buffer 满了发不出去
│
└─→ 症状链 2(rx 侧):node4 发出 44x 小包 → peer 节点 NIC rx ring 溢出
→ peer 侧 rx drops
关键区分:
| TcpRetransFail | rx 丢包 | |
|---|---|---|
| 发生在哪 | node4 本地(发送端) | peer 节点(接收端) |
| 机制 | sk_wmem_alloc > sk_sndbuf,重传包没进 qdisc | NIC rx ring buffer / softirq budget 耗尽 |
| 算不算丢包 | 不算,包根本没到线路上 | 算,包到了 NIC 但被丢弃 |
| 谁的 counter | node4 的 TcpRetransFail | peer 的 ethtool -S rx_dropped / ifconfig RX dropped |
所以 "丢包低但重传炸" 不矛盾:
- node4 tx 不会有 wire drops — 小包确实发出去了,NIC 能发 1460B 的包没问题
- node4 TcpRetransFail 不是丢包 — 是重传包在本地 socket buffer 层就被拒绝了
- peer rx drops 可能有一些 — 但 44x 小包未必超过现代 NIC ring buffer 容量,所以 "丢包低"
一句话:tx off 的直接后果不是丢包,是段数爆炸导致 TCP 栈内部资源耗尽。线路是通的,NIC 是好的,包也确实发出去了 — 只是 TCP 重传机制被自己的 wmem 压力卡死了。