---
title: 网络性能与调优
url: https://doc.liz6.com/networking/13-network-diagnostics/03-network-performance-and-tuning
locale: zh
area: networking
tags:
- networking
- 网络诊断
date: 2026-06-30
modified: 2026-07-16
description: 从应用 buffer 到 TCP 拥塞控制到网卡 offload——每层都有可调参数。BDP 决定需要多大 buffer 跑满带宽,BBR 用模型替代丢包检测在 bufferbloated 链路上优于 CUBIC,fq_codel/cake 主动管理队列防止 buffer 过度填满。
---

# 网络性能与调优

> 从应用 buffer 到 TCP 拥塞控制到网卡 offload——每层都有可调参数。BDP 决定需要多大 buffer 跑满带宽,BBR 用模型替代丢包检测在 bufferbloated 链路上优于 CUBIC,fq_codel/cake 主动管理队列防止 buffer 过度填满。

## 概述

网上流传的"TCP 调优秘籍"大多是不看场景的 sysctl 堆砌。有效的调优只有一条路：**先搞清数据从应用到网线要经过哪些队列，测出瓶颈在哪一层，再动那一层的旋钮**。一个包的发送路径上每一站都可能成为瓶颈，也各有各的调法：

```mermaid
flowchart LR
    APP["📦 应用 buffer<br><small>write 多大块</small>"] --> SK["socket buffer<br><small>tcp_wmem / tcp_rmem</small>"]
    SK --> CC["TCP 拥塞控制<br><small>cubic / bbr</small>"]
    CC --> QD["qdisc 队列<br><small>fq_codel / cake</small>"]
    QD --> DRV["驱动 ring buffer<br><small>ethtool -G</small>"]
    DRV --> NIC["网卡 offload<br><small>TSO / GSO / GRO</small>"]
    NIC --> NET(("🌐 网络<br><small>这里的队列你管不着<br>→ bufferbloat</small>"))
    classDef knob fill:#4493f81f,stroke:#4493f8,stroke-width:2px
    classDef wild fill:#f8514926,stroke:#f85149,stroke-width:2.5px
    class APP,SK,CC,QD,DRV,NIC knob
    class NET wild
```

三类典型问题对应三个不同的层：**带宽跑不满**多半是 socket buffer 小于 BDP 或拥塞算法不适配；**加载时延迟飙升**是队列问题（bufferbloat）；**小包吞吐/CPU 打满**是中断和 offload 问题。下面按这三类展开。

## 跑不满带宽:BDP 与 Buffer Sizing

TCP 想跑满一条链路，"在路上飞着"的未确认数据必须填满整个管道。管道容量就是 **BDP（带宽 × RTT）**，而 in-flight 上限受制于 `min(cwnd, 接收窗口, 发送 buffer)`——三者任何一个小于 BDP，带宽就跑不满。

| 链路 | BDP | Linux 默认够吗 |
|---|---|---|
| 1Gbps × 30ms(国内跨省) | 3.75 MB | 够(tcp_rmem max 默认 ~6MB) |
| 1Gbps × 150ms(跨洋) | 18.75 MB | **不够** |
| 10Gbps × 200ms | 250 MB | **远远不够** |

```bash
# 三元组: min / default / max —— 自动调节只在 min..max 之间伸缩
sysctl net.ipv4.tcp_rmem="4096 131072 67108864"   # 接收侧, 64MB 上限
sysctl net.ipv4.tcp_wmem="4096 16384 67108864"    # 发送侧
sysctl net.core.rmem_max=67108864                  # SO_RCVBUF 手动设置的上限
```

验证手段:`ss -ti` 看单连接的 `cwnd`、`rtt`、`bytes_acked`——如果 cwnd 长期顶着 buffer 上限而不是随链路波动,就是 buffer 卡了脖子。

**注意**:buffer 不是越大越好。大 buffer 在低 BDP 链路上只是白占内存,在自己就是瓶颈的设备上还会加重排队延迟。按实际要跑的最大 BDP 设,留 2 倍余量即可。

## 加载时延迟飙升:Bufferbloat 与队列管理

经典症状：**空闲时 ping 10ms，下载一开跑 ping 涨到 500ms**。原因不在带宽，在队列——路径上某个设备（往往是家用路由器/光猫）配了巨大的 FIFO buffer，CUBIC 这类丢包驱动的算法会一直发包直到填满它才收手，于是所有流量都在这个深队列里排队。

自己能控制的两端，用 **AQM（主动队列管理）**替代 FIFO：

```bash
tc qdisc replace dev eth0 root fq_codel              # 多数发行版已默认
tc qdisc replace dev eth0 root cake bandwidth 950mbit # 家庭出口首选
```

- **fq_codel** = 公平队列 + CoDel。CoDel 的思路是盯住包的*驻留时间*：持续超过 5ms 就开始丢包（从队头丢，信号传得快），逼发送方降速——队列短了，延迟自然下来。公平队列部分保证一条大象流不会饿死旁边的 ssh。
- **cake** 是 fq_codel 的工程完善版：内置整形器（把 `bandwidth` 设成略低于真实上行,把队列从光猫抢回自己手里）、per-host 隔离（一台设备开 BT 不拖累全家）、DiffServ 感知。

发送侧配合 `bbr` 效果最好——BBR 本身就不填队列（原理见 [TCP 深度](/networking/03-l4-transport-layer/01-tcp.md)的拥塞控制一节）：

```bash
sysctl net.ipv4.tcp_congestion_control=bbr
```

## 常用 TCP 旋钮

| 旋钮 | 作用 | 何时动它 | 风险 |
|---|---|---|---|
| `tcp_congestion_control=bbr` | 模型驱动的拥塞控制 | 长肥/有丢包/bufferbloat 链路 | 与 CUBIC 流共存时的公平性(v1) |
| `tcp_fastopen=3` | SYN 里带数据,回头客省 1 RTT | 短连接密集的 API 服务 | 中间盒可能丢 TFO 包 |
| `tcp_tw_reuse=1` | 客户端复用 TIME_WAIT | 高并发短连接的*客户端* | 仅客户端安全;别碰早已移除的 tw_recycle |
| `somaxconn` + `tcp_max_syn_backlog` | 全/半连接队列深度 | `ss -lnt` 看到 listen 队列溢出 | 只是加深队列,处理不过来还得扩容 |
| `tcp_notsent_lowat=16384` | 限制发送 buffer 里"还没发出去"的量 | 延迟敏感的流式应用 | 极端吞吐略降 |

原则:**每改一个,压测对比一次**。旋钮之间会互相作用,一把梭等于没调。

## 小包与 CPU:网卡 Offload

10Gbps 下每秒可达百万包,逐包中断 CPU 会先于带宽爆掉。思路是**批处理**——把分段/合并的活从内核搬到网卡:

| Offload | 方向 | 做什么 |
|---|---|---|
| TSO | 发送 | 内核丢给网卡一个 64KB 大块,网卡硬件切成 MSS 大小 |
| GSO | 发送 | TSO 的软件兜底(虚拟网卡、不支持 TSO 的硬件),在驱动前最后一刻才分段 |
| GRO | 接收 | 把同一条流的连续小包合并成大块再上交协议栈,中断和遍历都省了 |

```bash
ethtool -k eth0 | grep -v fixed     # 查看当前 offload 状态
ethtool -K eth0 tso off             # 排查问题时临时关闭
ethtool -G eth0 rx 4096 tx 4096     # 驱动 ring buffer, 突发流量丢包时加大
```

**tcpdump 的经典陷阱**:抓包看到 64KB 的"巨包"不是异常——那是 TSO/GRO 生效,抓包点在分段之前/合并之后;线上实际以 MTU 尺寸传输。反过来,怀疑 offload 引发问题(尤其虚拟化环境的 checksum 错误)时,先 `ethtool -K ... off` 排除。

## 定位瓶颈的顺序

1. **基线**:`iperf3 -c <server> -R` 双向各测,拿到裸带宽——先确认瓶颈到底在不在网络。
2. **加载下测延迟**:一边 iperf3 一边 `ping`,延迟涨十倍 → bufferbloat,去调队列。
3. **看单连接**:`ss -ti` 的 cwnd/rtt/retrans——cwnd 上不去看 buffer 与拥塞算法,retrans 多去查链路质量。
4. **看系统**:`nstat -a | grep -i retrans`、`ethtool -S eth0 | grep -i drop`(网卡层丢包)、`softnet_stat` 背压。
5. 改一个变量,回到第 1 步。

系统性的抓包分析见 [tcpdump 与 Wireshark](/networking/13-network-diagnostics/01-tcpdump-and-wireshark.md),故障排查思路见[网络故障诊断方法论](/networking/13-network-diagnostics/02-network-troubleshooting-methodology.md)。

## 参考

- **BBR**: github.com/google/bbr · RFC 9438
- **fq_codel/CoDel**: RFC 8290, RFC 8289 · bufferbloat.net
- **cake**: man tc-cake · RFC 8290 的工程化后续
- **Brendan Gregg**: "Systems Performance" 第 10 章 (Network)

*Keywords: BDP, BBR, CUBIC, bufferbloat, fq_codel, cake, AQM, TCP tuning, tcp_rmem, TSO, GSO, GRO, ethtool, iperf3*
