NNetpath
原理预计 30 分钟

一个包的旅程:从 send() 到网线

把发包和收包路径拆成可观测的若干段,后面所有排障都是在这条路径上定位。

学完这节你能做到

  • 说出发包路径上的关键环节:socket 缓冲、qdisc、驱动环形队列、DMA
  • 说出收包路径上的关键环节:DMA、硬中断、软中断/NAPI、协议栈、socket 队列
  • 拿到一个丢包现象时,知道该去哪一层查计数器

建议先学跳过这几节会看不懂本节的部分推导

为什么先背路径

"网络慢"这三个字信息量几乎为零。它可能是应用没及时读、可能是本机队列满了、可能是网卡在丢包、也可能是交换机上行拥塞。

区别在于:如果你脑子里有一张路径图,报障就会自动变成一道"排除题"——每一段都有对应的计数器,看一眼就能划掉一段。没有这张图,你只能从"重启一下试试"开始。

所以这节课不讲优化,只做一件事:把发包和收包路径拆成可观测的若干段,并记住每一段用什么命令看。

i路径是一切的地基

后面 L1 讲容器网络、L2 讲 RDMA,本质都是在这条路径上做手术:CNI 在中间插了一段虚拟设备, RDMA 则是把中间整段切掉。不先看清原版,就看不出它们改了什么。

发送方向:四个队列

调用一次 send(),数据并不会立刻上网线。它要依次经过四个排队点:

应用 buffer
   │ send()            ← 一次拷贝 + 一次上下文切换
   ▼
socket sndbuf          ← 队列 1:受窗口与拥塞控制约束
   │ TCP 分段、加头、查路由
   ▼
qdisc 排队规则          ← 队列 2:限速、优先级、主动丢包
   │
   ▼
驱动 tx ring           ← 队列 3:满了就停止投递
   │ 网卡 DMA 取数据
   ▼
网卡串行化上线          ← 队列 4:物理线速就是硬上限

发送方向的丢包几乎全在这些队列上,而且各有各的计数器:

排队点满了会怎样怎么看
socket sndbufsend() 阻塞或返回 EAGAINss -timSend-Q
qdisc主动丢包tc -s qdisc show dev bond0dropped
tx ring内核停止投递,严重时网卡复位ethtool -Stx_droppeddmesg 看 tx timeout
物理线速排队延迟上升ethtool bond0 | grep Speed
Send-Q 长期不降,别在本机找原因

Send-Q 是"我想发但发不出去"的量。它高说明对端收得慢或者路径在丢包, 瓶颈在对端或中间,不在这台机器。这时候调本机内核参数是白费功夫。

接收方向:更容易丢

收包比发包脆弱,因为四个环节的速度必须互相匹配:网卡收得快、CPU 取得慢,中间就会丢。

网线
   │ 网卡校验、RSS 哈希选队列
   ▼
DMA 写入 rx ring        ← 没有空描述符 → 直接丢
   │ 硬中断(可合并)
   ▼
NAPI 软中断轮询          ← 预算用尽 / backlog 满 → 丢
   │ GRO 合包、netfilter、路由、TCP 状态机
   ▼
socket rcvbuf           ← 应用不读 → 窗口缩小到 0
   │ read()
   ▼
应用

三个必看计数器,按出现顺序:

# 1. 网卡来不及 DMA:CPU 回收描述符跟不上收包速度
ethtool -S bond0 | grep -iE 'rx_missed|rx_no_buf|discard'

# 2. 软中断跟不上:第 2 列是 backlog 丢包,第 3 列是预算用尽
cat /proc/net/softnet_stat

# 3. 应用读得慢:Recv-Q 持续堆积
ss -tim | grep -A1 ':8080'
×Recv-Q 高的时候优化网络是徒劳

Recv-Q 堆积意味着数据早就到了本机,是应用没来取。这种"网络慢"的报障里, 真正该改的是应用的处理逻辑或线程模型。先看这个计数器,能省下大量白干的时间。

硬中断只是"敲门"

一个常见误解是"网卡收一个包就中断一次 CPU"。实际上:

  • 硬中断只负责登记"有活儿了",几乎不干活
  • 真正的取包在 NET_RX 软中断里批量完成,这就是 NAPI
  • 中断合并(coalescing)会攒一批再敲门,用一点延迟换大幅下降的 CPU 开销

所以看到某个核 %soft 跑到 100%、其它核闲着,不是"网络忙",是中断亲和性没配好, 包全散到了一个核上。

cat /proc/interrupts | grep -i mlx   # 中断落在哪些 CPU 上
mpstat -P ALL 1                      # 看 %soft 的分布
ethtool -c bond0                     # 当前的中断合并参数

自己走一遍

下面把两个方向的路径都放进来。点开每一跳,看它干了什么、用什么命令看、典型怎么坏。

路径推演一个包经过的每一跳

一次 send() 之后,数据要经过四个队列才上得了网线。

6延迟量级 微秒级(本机部分)
这条路径要记住的一句话

发送方向的丢包几乎都在队列上:sndbuf 满、qdisc 满、ring 满,三处各有各的计数器。

检查点

检查点单选

应用调用 send() 后返回 EAGAIN,最直接说明什么?

检查点单选

ethtool -S 里 rx_missed_errors 持续增长,最合理的解释是?

检查点多选

下面哪些排队点在发送方向上会主动丢包?(多选)

这节课的落点

  • 排障的第一动作不是猜,是在路径上定位:发送有四个排队点,接收有四个环节
  • 发送方向看 Send-Qtc -s qdiscethtool -S 的 tx 计数
  • 接收方向看 ethtool -S 的 rx_missed、/proc/net/softnet_statRecv-Q
  • 硬中断只登记,实际取包在软中断里做(NAPI);单核 %soft 打满是亲和性问题
  • Recv-Q 堆积说明该优化应用,不是优化网络

延伸资料

  • ·Systems Performance, 2nd Edition — Brendan Gregg
  • ·k8s-in-actionos/os.md