Linux 网络性能全景:从协议栈、C10K 到 NAT、抓包与内核调优

Linux 网络性能全景:从协议栈、C10K 到 NAT、抓包与内核调优

Linux 网络性能问题很少是单点问题。

一个看起来只是“接口变慢”的故障,可能发生在 DNS 解析、TCP 建连、套接字缓冲区、协议栈、软中断、网卡队列、NAT 连接跟踪,甚至应用程序自己的 I/O 模型中。也正因为如此,网络性能分析不能只靠某一个命令,更不能看到 CPU 高就立刻调 CPU,看到延迟高就直接改 TCP 参数。

更可靠的方法,是先建立一条完整的心智模型:

应用程序 -> Socket -> TCP/UDP -> IP -> 链路层 -> 网卡 -> 物理网络

然后沿着这条链路回答三个问题:

  1. 数据包现在走到了哪里?
  2. 这一层应该观察什么指标?
  3. 指标异常后,应该用什么工具继续下钻?

本文围绕这条主线,把 Linux 网络协议栈、收发路径、性能指标、C10K/C1000K、高并发 I/O 模型、基准测试、DNS、抓包、DDoS、网络延迟、NAT 与分层优化方法组织成一个完整体系。


一、先建立网络协议栈的整体模型

1. OSI 七层与 TCP/IP 四层

网络协议必须解决两个问题:

  • 不同厂商、不同硬件、不同系统之间如何互联;
  • 一个网络包从应用程序到网卡的复杂处理流程如何解耦。

OSI(Open System Interconnection)将网络通信抽象为七层:

OSI 层 主要职责 常见理解
应用层 为应用提供网络服务 HTTP、DNS、FTP
表示层 数据格式转换 编码、序列化、加密表示
会话层 维护通信会话 会话建立与管理
传输层 端到端传输 TCP、UDP
网络层 寻址、路由、转发 IP、ICMP
数据链路层 MAC 寻址、帧传输、错误检测 Ethernet
物理层 比特在物理介质上传输 网线、光纤、射频

Linux 实际使用的模型更接近 TCP/IP 四层:

TCP/IP 层 对应 OSI 典型协议/组件
应用层 应用层 + 表示层 + 会话层 HTTP、DNS、FTP
传输层 传输层 TCP、UDP
网络层 网络层 IP、ICMP
网络接口层 数据链路层 + 物理层 Ethernet、MAC、网卡驱动

工程交流中经常说“四层负载均衡”“七层负载均衡”,这里通常沿用的是 OSI 的层级语义:

  • 四层负载均衡:主要基于 TCP/UDP、IP、端口转发;
  • 七层负载均衡:可以理解 HTTP Host、URI、Header 等应用层信息。

2. 网络包为什么要逐层封装

应用程序发送的数据不会原样直接落到网线上,而是逐层添加协议元数据。

以 HTTP over TCP 为例:

1
2
3
4
应用层:                  [ HTTP/应用数据 ]
传输层: [ TCP Header ][ HTTP/应用数据 ]
网络层: [ IP Header ][ TCP Header ][ HTTP/应用数据 ]
链路层:[ Frame Header ][ IP ][ TCP ][ Data ][ Frame Trailer ]

接收端执行相反过程:逐层解析头部、确认上层协议并解封装。

可以用 Mermaid 表示为:

flowchart TD
    A[应用数据] --> B[传输层: 增加 TCP/UDP 头]
    B --> C[网络层: 增加 IP 头并进行路由]
    C --> D[链路层: 增加帧头/帧尾]
    D --> E[网卡发送]

    E2[网卡接收] --> D2[链路层解帧]
    D2 --> C2[IP 层解析与路由判断]
    C2 --> B2[TCP/UDP 定位 Socket]
    B2 --> A2[应用程序读取数据]

3. MTU:为什么“包太大”会影响性能

物理链路不能无限制地传输任意大小的数据包。MTU(Maximum Transmission Unit,最大传输单元)定义了接口允许承载的最大 IP 包大小。

传统以太网最常见的 MTU 是:

1
1500 Bytes

如果 IP 包超过链路允许的 MTU,就可能发生分片或触发与路径 MTU 相关的处理。分片越多,意味着:

  • 更多包头开销;
  • 更高 PPS;
  • 更多协议栈处理;
  • 丢一个分片可能影响整个报文重组。

因此,网络吞吐优化不能只看“带宽有多大”,还要看包有多大

叠加网络尤其容易踩 MTU 的坑。以 VXLAN 为例,在原始报文外又增加 Ethernet、IP、UDP、VXLAN 等头部。如果底层网络仍只允许 1500B,常见做法是把虚拟网卡 MTU 调低,例如 1450 左右,或者让底层网络支持更大的 MTU。


二、Linux 网络栈到底如何收发一个包

Linux 网络栈可以抽象为下面几层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
应用程序
|
系统调用
|
Socket 层
|
TCP / UDP / ICMP
|
IP / 路由
|
ARP / 链路层
|
网卡驱动
|
NIC

这里最容易被忽略的是:网络路径不仅是协议处理,还包含系统调用、中断、DMA、内存缓冲区与 CPU 调度。

1. 接收路径

一个典型的物理网卡收包过程可以概括为:

  1. 网络帧到达网卡;
  2. 网卡通过 DMA 把数据写入驱动管理的接收 Ring Buffer;
  3. 网卡产生硬中断,通知 CPU 有新数据;
  4. 硬中断只做尽量少的工作,把更多处理推迟到软中断;
  5. 内核用 sk_buff 等数据结构表示网络包;
  6. 链路层检查帧并识别上层协议;
  7. IP 层解析地址、协议号并做路由判断;
  8. TCP/UDP 根据连接信息找到对应 Socket;
  9. 数据进入 Socket 接收缓冲区;
  10. 应用程序通过 recv()read() 等系统调用读取数据。

2. 发送路径

发送方向基本与接收相反:

  1. 应用调用 send()sendmsg() 等 Socket API;
  2. 系统调用进入内核;
  3. 数据进入 Socket 发送缓冲区;
  4. TCP/UDP 添加传输层头部;
  5. IP 层添加 IP 头并查询路由、确定下一跳;
  6. 根据 MTU/MSS 等规则进行必要的分段或分片处理;
  7. 链路层解析下一跳 MAC,封装以太网帧;
  8. 数据进入发送队列;
  9. 驱动通过 DMA 让网卡读取待发送数据;
  10. 网卡把帧发送到物理网络。

3. 硬中断与软中断为什么要拆开

如果所有协议处理都放在网卡硬中断里,CPU 会长时间停留在中断上下文中,其他任务很难获得运行机会。

Linux 因此采用“硬中断做快事、软中断做重活”的思路:

  • 硬中断:快速确认设备事件,完成最必要的处理;
  • 软中断:继续执行协议栈的大部分收发逻辑。

系统中每个 CPU 都有对应的 ksoftirqd/N 内核线程,例如:

1
2
3
ksoftirqd/0
ksoftirqd/1
...

但不能把“网络协议栈”等同于某一个内核线程。完整网络处理还会涉及硬中断、软中断、驱动、内存分配、kworker 等多种内核机制。

4. Ring Buffer、sk_buff、Socket Buffer 到底在哪里

这三个概念经常混淆。

Ring Buffer

网卡通过 DMA 与内存交换数据时使用的环形队列,归网卡驱动管理。它不是某个用户进程的内存。

sk_buff

sk_buff 是 Linux 网络协议栈最核心的数据结构之一,用于描述和管理网络包。不同协议层之间传递数据时,很多操作只需要移动数据结构中的指针,而不需要每经过一层都复制完整数据。

Socket Buffer

每个 Socket 都有发送和接收缓冲区:

  • 接收缓冲区保存已经到达、但应用还没取走的数据;
  • 发送缓冲区保存应用已经写入、但尚未完成发送/确认的数据。

这些缓冲区同样位于内核管理的内存中,并不是简单映射成用户空间的一块数组。

sk_buff、Socket 缓冲区、连接跟踪对象等大量网络内核对象通常由 slab 分配器管理,可以从 /proc/slabinfo 观察相关内存占用。


三、网络性能不是一个数字:先建立指标坐标系

分析网络性能,至少要同时关注以下指标。

指标 含义 常见单位 典型问题
带宽 Bandwidth 链路理论最大传输速率 bit/s 网卡/链路上限
吞吐量 Throughput 单位时间成功传输的数据量 bit/s、B/s 是否跑满链路
PPS 每秒网络包数 packet/s 小包、转发、软中断压力
延迟 Latency 请求或报文往返时间 ms、us RTT、握手、应用响应
并发连接数 同时存在的连接数量 connections C10K/C1000K
丢包率 丢失报文比例 % 队列满、链路异常
重传率 TCP 重传报文比例 % 拥塞、丢包、接收能力不足
错误数 网卡/协议错误 packets 校验、carrier、frame 等
可用性 能否成功通信 - 路由、防火墙、DNS、服务异常

1. 带宽与吞吐量不是一回事

“千兆网卡”说的是理论链路带宽,不代表应用一定能得到 1Gbps 吞吐。

粗略地说:

1
链路利用率 = 实际吞吐量 / 理论带宽

sar 中的 %ifutil 也可以用来估算接口使用率。资料中的计算方式为:

1
2
半双工: (rx + tx) / Bandwidth
全双工: max(rx, tx) / Bandwidth

2. PPS 为什么对小包特别重要

同样是 1Gbps:

  • 如果每个包很大,需要处理的包数量较少;
  • 如果都是 64B 小包,CPU 需要处理的包数量会暴涨。

因此网络转发、DDoS、虚拟交换、NAT 网关等场景不能只看 BPS,还必须看 PPS。

经典千兆以太网 64B 小包的理论线速 PPS 可以近似估算为:

1
2
PPS ≈ 1000 Mbit/s / ((64 + 20) * 8 bit)
≈ 1.488 Mpps

其中额外的 20B 用来近似计算前导码与帧间距等线速开销。


四、第一轮排查:先看接口、队列和协议栈

1. 看网络接口:优先使用 ip

传统命令 ifconfig 来自 net-toolsip 来自 iproute2。两者都能看接口信息,但 ip 的能力更完整。

1
ip -s addr show dev eth0

重点检查:

  • 接口是否 UP
  • 是否存在 LOWER_UP,即物理链路是否真正连通;
  • MTU 是否合理;
  • IP、子网、MAC 是否正确;
  • RX/TX 的 packets、bytes、errors、dropped 等是否持续增长。

也可以使用:

1
ifconfig eth0

常见异常指标:

指标 说明
errors 校验错误、帧同步错误等
dropped 包已经进入接收路径,但因资源不足等原因被丢弃
overruns Ring Buffer 来不及处理,队列溢出
carrier 链路/双工/物理介质等 carrier 问题
collisions 以太网碰撞计数

这些计数大多是累积值。排障时最重要的不是“是否大于 0”,而是是否持续增加

2. 看网卡能力

1
ethtool eth0

只想看速率:

1
ethtool eth0 | grep Speed

虚拟网卡、云主机或某些驱动环境不一定能返回真实物理带宽,因此工具输出需要结合实际网络架构理解。

3. 看吞吐量与 PPS

1
sar -n DEV 1

典型字段:

1
2
3
4
rxpck/s  接收 PPS
txpck/s 发送 PPS
rxkB/s 接收吞吐量
txkB/s 发送吞吐量

如果看到:

  • BPS 很低,但 PPS 极高:优先怀疑小包洪泛、扫描、异常转发;
  • BPS 接近带宽上限:优先看链路是否真的饱和;
  • PPS 与软中断 CPU 同时升高:继续检查网卡队列、中断分布和协议栈。

4. 看 Socket:优先使用 ss

1
2
3
ss -s
ss -ltnp
ss -tanp

建立连接状态下:

  • Recv-Q:应用还没读走的数据;
  • Send-Q:已经发送但尚未被对端确认的数据。

如果这两个值持续很大,说明发生了积压:

  • Recv-Q 大:应用处理太慢、线程阻塞、消费不及时;
  • Send-Q 大:对端接收慢、链路拥塞、丢包重传或发送路径受阻。

LISTEN 状态下的 Recv-Q / Send-Q

这是一个很容易被旧资料误导的点。

对现代 Linux 的 ss -lnt 输出,通常应理解为:

  • Recv-Q:当前已完成握手、等待 accept() 的全连接队列长度;
  • Send-Q:该监听 Socket 的全连接队列最大长度。

半连接队列则是收到 SYN、但尚未完成三次握手的连接集合,主要受 tcp_max_syn_backlog 等机制影响。

5. 看协议栈统计

1
2
netstat -s
ss -s

关注:

  • active/passive connection openings;
  • failed connection attempts;
  • connection resets;
  • segments sent/received;
  • retransmitted;
  • checksum errors。

网络性能分析到这里,已经不是“网卡有没有流量”的问题,而是在确认:数据究竟堵在接口、协议栈还是 Socket。


五、从 C10K 到 C10M:高并发真正卡在哪里

C10K 中的 C 可以理解为 Client/Connection。它讨论的是:一台机器如何同时处理 1 万级连接。

C100K、C1000K、C10M 则把数量级继续推到 10 万、100 万和 1000 万。

1. C10K 的根因不是“机器太慢”,而是 I/O 模型不合适

早期服务器即使只有 2GB 内存和千兆网卡,理论上也足以容纳 1 万个很轻量的连接。真正的问题是传统的“一个连接一个进程/线程”模型:

1
2
3
4
5
10000 connections

10000 processes/threads

大量栈内存 + 调度 + 上下文切换

要解决 C10K,核心是两个问题:

  1. 一个线程如何同时等待多个 Socket?
  2. 如何用更少的线程服务更多连接?

2. 水平触发与边缘触发

I/O 多路复用之前,先区分两种事件通知方式。

Level Triggered(LT,水平触发)

只要 fd 当前仍然可读/可写,就可以持续得到通知。

优点是编程简单,不容易漏事件。

Edge Triggered(ET,边缘触发)

只有 fd 状态发生变化时通知一次。

ET 下收到通知后,应用需要尽可能把数据读/写到 EAGAIN,否则没有处理完的内容可能不会再次触发同样的边缘事件。

3. select、poll、epoll 的本质区别

模型 fd 管理 查找就绪 fd 主要问题
select 固定大小位图 线性扫描 fd 数量限制、O(N) 扫描
poll 动态数组 线性扫描 无固定 1024 限制,但仍 O(N)
epoll 内核维护事件集合 事件驱动 更适合大量 fd

select/poll 每次调用还要在用户态和内核态之间传递 fd 集合;连接越多,成本越高。

epoll 的关键改进是:

  • fd 集合长期保存在内核中;
  • 只返回真正发生事件的 fd;
  • 不必每次完整扫描所有连接。

这就是 Linux 解决 C10K 的核心机制之一。

4. AIO 与 I/O 多路复用不是一回事

epoll 解决的是“哪些 fd 已经可以执行 I/O”。真正的读写仍由应用发起。

AIO(Asynchronous I/O)则是应用发起操作后继续执行,完成后由系统通知结果。

在网络程序里,I/O 多路复用长期是更常见的高并发基础模型。AIO 的控制流更复杂,边界条件也更多。

5. 高性能服务为什么常用 master + worker

典型模式:

flowchart TD
    M[Master/Main Process] --> L[bind + listen]
    L --> W1[Worker 1]
    L --> W2[Worker 2]
    L --> W3[Worker 3]
    W1 --> E1[epoll / accept]
    W2 --> E2[epoll / accept]
    W3 --> E3[epoll / accept]

以 Nginx 一类程序为例:

  • master 负责初始化、配置加载与 worker 生命周期;
  • worker 长期存在,有任务时被唤醒,无任务时休眠;
  • 不会为每个请求重复创建进程。

因此,多进程并不等于频繁 fork,也不必然意味着高上下文切换成本。

6. 惊群、EPOLLEXCLUSIVESO_REUSEPORT

多个 worker 同时等待同一个事件时,可能出现“惊群”:一个事件唤醒很多进程,最终只有一个能真正处理。

Linux 和应用层逐步对这个问题进行了优化:

  • accept() 的经典惊群问题在较早内核中已经改善;
  • epoll 后续引入 EPOLLEXCLUSIVE
  • Nginx 也曾通过 accept_mutex 控制 worker 竞争;
  • Linux 3.9 以后可使用 SO_REUSEPORT,让多个 Socket 绑定相同地址与端口,由内核做请求分配。

SO_REUSEPORT 模型大致是:

flowchart TD
    K[Kernel] --> S1[Socket Listener 1]
    K --> S2[Socket Listener 2]
    K --> S3[Socket Listener 3]
    S1 --> W1[Worker 1]
    S2 --> W2[Worker 2]
    S3 --> W3[Worker 3]

7. 从 C100K 到 C1000K:开始进入系统工程

到了百万连接,I/O 模型已经不再是唯一瓶颈。

假设 100 万连接每个只消耗 16KB:

1
1,000,000 * 16KB ≈ 15.3GB

假设其中 20% 是活跃连接,每个活跃连接平均 1KB/s:

1
2
3
1,000,000 * 20% * 1KB/s
≈ 200MB/s
≈ 1.6Gbps

此时要同时考虑:

  • 文件描述符数量;
  • Socket/TCP 缓冲区;
  • conntrack;
  • 内存容量;
  • 网卡带宽;
  • 多队列网卡;
  • 中断亲和性;
  • RPS/RFS;
  • TSO/GSO/GRO 等 Offload;
  • 应用工作模型。

8. C10M:为什么“继续调 sysctl”最终会失效

当并发继续上升到千万级,传统内核网络路径本身就可能成为瓶颈:

1
2
3
4
5
6
7
8
9
10
NIC
-> hard IRQ
-> softirq
-> driver
-> skb
-> IP
-> TCP/UDP
-> Socket
-> system call
-> application

每个包都走这么长的路径,即使每一层只消耗一点 CPU,累积后也非常可观。

这时通常出现两类更激进的数据路径。

DPDK

DPDK 的思路是绕过传统内核网络协议栈,由用户态程序直接轮询网卡队列。

典型优化包括:

  • Poll Mode Driver;
  • Huge Pages;
  • CPU 绑定;
  • 内存对齐;
  • NUMA 感知;
  • 批处理和流水线。

轮询并不总是低效。当 PPS 极高、几乎一直有包可处理时,避免频繁中断反而可能更高效。

XDP

XDP(eXpress Data Path)基于 eBPF,可以在报文进入完整内核协议栈之前处理它。

适合:

  • DDoS 过滤;
  • 高性能 ACL;
  • 负载均衡;
  • 容器网络数据面;
  • 高速报文丢弃与重定向。

对大多数业务系统来说,真正合理的结论不是“单机一定要做到 C10M”,而是:在单机优化复杂度开始失控之前,优先通过水平扩展分散请求。


六、性能优化前先做基准测试:按协议层测,而不是只跑一个压测工具

网络性能评估最重要的原则是:

你要先知道自己在测试协议栈的哪一层。

不同层有不同的性能上限和测试工具。

层级 重点指标 常用工具
网络接口/网络层 PPS pktgenhping3
传输层 TCP/UDP 吞吐、丢包、抖动 iperf3netperf
HTTP 层 QPS、延迟、吞吐 abwrk
真实业务层 业务 QPS、P95/P99、错误率 wrk Lua、JMeter、LoadRunner、流量回放

底层性能决定上层性能上限。如果网络层只能处理 10 万 PPS,应用层不可能凭空突破底层包处理能力。

1. 用 pktgen 测 PPS

pktgen 是 Linux 内核自带的发包框架。加载模块后:

1
2
modprobe pktgen
ls /proc/net/pktgen/

通常会看到:

1
2
3
4
kpktgend_0
kpktgend_1
...
pgctrl

一个简化的 64B 发包配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function pgset() {
local result
echo "$1" > "$PGDEV"
result=$(grep "Result: OK:" "$PGDEV")
if [ -z "$result" ]; then
grep "Result:" "$PGDEV"
fi
}

PGDEV=/proc/net/pktgen/kpktgend_0
pgset "rem_device_all"
pgset "add_device eth0"

PGDEV=/proc/net/pktgen/eth0
pgset "count 1000000"
pgset "clone_skb 0"
pgset "pkt_size 64"
pgset "dst 192.168.0.30"
pgset "dst_mac 11:11:11:11:11:11"

PGDEV=/proc/net/pktgen/pgctrl
pgset "start"

结果可以从:

1
cat /proc/net/pktgen/eth0

读取,重点看:

  • packets;
  • PPS;
  • Mbit/s;
  • errors。

2. 用 iperf3 测 TCP/UDP 吞吐

服务端:

1
iperf3 -s -i 1 -p 10000

客户端:

1
iperf3 -c 192.168.0.30 -b 1G -t 15 -P 2 -p 10000

重点关注最终的:

  • Transfer
  • Bitrate/Bandwidth
  • TCP 重传;
  • UDP 丢包与 jitter。

不要把 iperf3 测出来的吞吐量叫“网卡带宽”。网卡带宽是物理能力,iperf 测到的是这条链路在当前条件下的实际吞吐能力。

3. 用 ab 看最基础的 HTTP 性能

1
ab -c 1000 -n 10000 http://192.168.0.30/

重点字段:

1
2
3
4
5
Requests per second
Time per request
Transfer rate
Connection Times
Percentage of the requests served within a certain time

HTTP 压测必须看分位数。平均值经常会掩盖长尾。

4. 用 wrk 构造更高效的 HTTP 压测

1
wrk --latency -c 1000 -t 2 http://192.168.0.30/

wrk 的优势不仅是自身性能高,还内置 LuaJIT,可以自定义请求。

例如通过脚本加入动态 Header、认证 token、请求路径和 payload:

1
2
3
4
5
6
7
request = function()
return wrk.format("GET", "/resource")
end

response = function(status, headers, body)
-- 在这里处理响应
end

运行:

1
wrk -c 1000 -t 2 -s test.lua http://192.168.0.30/

5. 基准测试最容易犯的错误

压测机先成为瓶颈

如果 ab 自己只能制造 1000 QPS,你无法用它证明服务端上限就是 1000 QPS。

测试场景和生产完全不同

静态首页的 QPS 并不能代表真实 API 的性能。真实请求可能包含:

  • JSON payload;
  • 鉴权;
  • 数据库访问;
  • RPC;
  • 动态响应体;
  • 长连接。

只看平均值

网络系统应至少同时观察:

1
2
3
4
5
6
QPS / BPS / PPS
P50 / P90 / P95 / P99
错误率
超时数
重传率
CPU softirq

七、DNS:很多“网络慢”其实在连接建立之前就已经慢了

DNS(Domain Name System)负责域名和 IP 地址之间的映射,也是网络访问链条中最容易被忽略的一步。

1. DNS 是分层系统

以:

1
time.geekbang.org

为例:

1
2
3
4
.              根
└── org 顶级域
└── geekbang 二级域
└── time 主机/子域

常见记录:

记录 作用
A 域名 -> IPv4
AAAA 域名 -> IPv6
CNAME 别名
NS 权威 DNS 服务器
MX 邮件服务器
PTR IP -> 域名反向解析

DNS 属于应用层,常见查询主要使用 UDP 53,特定场景也会使用 TCP。

2. Linux DNS 配置

最基础的 resolver 配置可以查看:

1
cat /etc/resolv.conf

例如:

1
nameserver 114.114.114.114

内网静态映射还可以放在:

1
cat /etc/hosts

3. nslookup:快速检查是否能解析

1
nslookup time.geekbang.org

如果解析失败,先确认:

  • /etc/resolv.conf 是否有 nameserver;
  • DNS 服务器 IP 是否可达;
  • UDP/TCP 53 是否被防火墙阻断;
  • 是否处于容器、Network Namespace 等独立网络环境。

4. dig +trace:看完整委派链

1
dig +trace +nodnssec time.geekbang.org

它会依次展示:

sequenceDiagram
    participant C as Client
    participant R as Recursive Resolver
    participant Root as Root DNS
    participant TLD as .org DNS
    participant Auth as geekbang.org DNS

    C->>R: A time.geekbang.org?
    R->>Root: NS org?
    Root-->>R: .org NS
    R->>TLD: NS geekbang.org?
    TLD-->>R: geekbang.org NS
    R->>Auth: A time.geekbang.org?
    Auth-->>R: A record
    R-->>C: IP + TTL

实际客户端一般只向递归 DNS 发一次查询,后面的权威链路由递归 DNS 代为完成,并通过缓存减少重复访问。

5. DNS 解析“时快时慢”怎么分析

一个实用的排查顺序:

1
2
3
4
5
6
7
8
9
10
11
DNS 是否配置

DNS Server 是否可达

RTT 是否过高 / 是否丢包

nslookup/dig 是否超时

是否命中缓存

是否需要更换 DNS / 本地缓存

先测延迟:

1
ping -c 3 <dns-server>

如果某个远端 DNS RTT 为 140ms 且偶发丢包,而另一台 DNS 只有 30ms,那么解析性能差异会非常明显。

6. DNS 缓存

使用本地缓存可以把大多数重复解析变为本地访问。

资料中的示例使用 dnsmasq

1
/etc/init.d/dnsmasq start

把本机 resolver 指向:

1
echo 'nameserver 127.0.0.1' > /etc/resolv.conf

第一次需要访问上游 DNS,后续在 TTL 有效期内可直接命中本地缓存。

DNS 优化常见思路:

  • 缓存;
  • 预取;
  • 使用延迟更低、更稳定的 DNS;
  • 内网部署自有 resolver;
  • 移动端等场景使用 HTTPDNS;
  • 通过 DNS/GSLB 返回更靠近用户的服务节点。

八、tcpdump + Wireshark:从“猜”升级为“看见报文”

很多网络问题,仅靠 pingsssar 无法定位,因为这些工具看到的是统计结果,而不是某一条连接真实发生了什么

抓包就是证据链。

1. tcpdump 的基本格式

1
tcpdump [options] [filter-expression]

常用选项:

选项 作用
-i eth0 指定接口
-i any 所有接口
-nn 不解析主机名和端口服务名
-c N 抓 N 个包后退出
-A ASCII 显示 payload
-e 显示链路层信息
-w file.pcap 写入 pcap 文件

2. 常用过滤表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 主机
tcpdump -nn host 192.168.0.30

# 目的端口
tcpdump -nn dst port 80

# TCP
tcpdump -nn tcp

# DNS 或指定主机
tcpdump -nn 'udp port 53 or host 35.190.27.188'

# SYN 包
tcpdump -nn 'tcp[tcpflags] & tcp-syn != 0'

3. tcpdump 输出怎么看

典型格式:

1
时间戳 协议 源IP.源端口 > 目的IP.目的端口 协议详细信息

DNS:

1
2
A? example.com.
PTR? x.x.x.x.in-addr.arpa.

TCP Flags 常见:

1
2
3
4
5
6
[S]   SYN
[S.] SYN + ACK
[.] ACK
[F.] FIN + ACK
[R] RST
[P.] PSH + ACK

4. 一个经典案例:ping 的 RTT 只有 30ms,为什么命令跑了 11 秒

抓包后发现:

1
2
3
4
5
6
7
DNS A 查询        正常
ICMP request 正常
ICMP reply 30ms
PTR reverse query 无响应
5s 后重试 PTR
又等待 5s
后续 ICMP 仍然 30ms

真正慢的不是 ICMP,而是 ping 在输出主机名时触发的 PTR 反向解析。

解决方法:禁止名称解析。

1
ping -n -c 3 geektime.org

这个案例最重要的不是 -n 参数,而是诊断方式:

工具显示的“总耗时”和协议本身的 RTT 不一致时,抓包去看工具背后额外做了什么。

5. 保存 pcap 给 Wireshark

1
tcpdump -nn 'udp port 53 or host 35.190.27.188' -w ping.pcap

然后把文件复制到带图形界面的机器:

1
scp server:/path/ping.pcap .

Wireshark 的优势是:

  • 自动解析协议层;
  • Follow TCP Stream;
  • 显示重传、Dup ACK、Zero Window 等分析提示;
  • Statistics -> Flow Graph;
  • Conversation/Endpoint 统计;
  • 可以直观看三次握手和连接关闭过程。

6. TCP 四次挥手为什么有时抓到三个包

理论过程通常写成:

1
2
3
4
Client -> FIN
Server -> ACK
Server -> FIN
Client -> ACK

但如果服务端收到 FIN 时本身也准备关闭连接,可以把 ACK 和自己的 FIN 合并:

1
2
3
Client -> FIN
Server -> FIN + ACK
Client -> ACK

因此,协议图描述的是允许的逻辑过程,真实抓包可能因为 ACK 合并等优化而减少包数。


九、网络延迟变大:不要只看 ping,要拆成“网络延迟 + 应用延迟”

1. 网络延迟与应用延迟

网络 RTT:

1
客户端 -> 网络 -> 服务端 -> 网络 -> 客户端

应用响应时间则是:

1
2
3
4
5
网络传输时间
+ 服务端排队时间
+ 应用处理时间
+ 下游依赖时间
+ 返回网络时间

所以:

1
HTTP 500ms != 网络 RTT 500ms

2. ICMP 被禁时怎么测

可以用 TCP SYN 测服务端口:

1
hping3 -c 3 -S -p 80 baidu.com

或者:

1
traceroute --tcp -p 80 -n baidu.com

这类测试比单纯 ICMP 更接近真正业务访问路径。

3. 单请求不慢,并发一上来就慢

一个很有价值的诊断模式是:

1
2
3
单次 TCP SYN RTT: 7ms
低并发 HTTP: 正常
并发 100: 平均延迟从 9ms 变成 40ms+

这说明链路基础 RTT 正常,问题更可能出现在:

  • 应用处理;
  • Socket 行为;
  • TCP 算法交互;
  • 队列。

这时用:

1
wrk --latency -c 100 -t 2 --timeout 2 http://server:8080/

再配合:

1
tcpdump -nn tcp port 8080 -w nginx.pcap

4. Delayed ACK + Nagle:两个“优化”叠加成 40ms 延迟

Delayed ACK 的目标是减少 ACK 包:收到数据后先等一小段时间,如果正好有数据要返回,就把 ACK 捎带出去。

Nagle 算法的目标是减少小包:当还有一个未确认的小分组在途时,暂缓继续发送新的小分组。

单独看都合理,但组合在一起可能形成等待:

sequenceDiagram
    participant S as Server (Nagle)
    participant C as Client (Delayed ACK)

    S->>C: Segment 1
    Note over C: 等待是否能捎带 ACK
    Note over S: 等 Segment 1 ACK,暂不发 Segment 2
    C-->>S: ACK(约 40ms 后)
    S->>C: Segment 2

strace 可以检查客户端是否设置了 TCP 选项:

1
strace -f wrk --latency -c 100 -t 2 --timeout 2 http://server:8080/

典型可看到:

1
setsockopt(..., SOL_TCP, TCP_NODELAY, ...)

Nginx 侧如果关闭了 tcp_nodelay

1
tcp_nodelay off;

就可能保留 Nagle 行为。改为:

1
tcp_nodelay on;

即可禁止 Nagle,让小响应不必等待前一个 ACK。

这类问题的关键经验是:

网络优化参数不是越多越好。两个单独看起来都能减少包数量的优化,组合后可能显著增加延迟。


十、NAT:性能问题的核心不是“改地址”,而是连接跟踪

NAT(Network Address Translation)通过修改 IP 包中的地址/端口,实现内外网映射。

常见分类:

1. 静态 NAT、动态 NAT、NAPT

  • 静态 NAT:内外 IP 一对一固定映射;
  • 动态 NAT:从公网地址池动态分配;
  • NAPT:多个内网 IP 通过不同端口共享公网 IP。

Linux 中最常见的是 NAPT。

2. SNAT、DNAT 与双向转换

SNAT

只改源地址/源端口,典型用途是内网访问外网。

1
2
3
192.168.0.2:50000
↓ SNAT
100.100.100.100:50000

DNAT

只改目的地址/目的端口,典型用途是把公网入口映射到内网服务。

1
2
3
100.100.100.100:8080
↓ DNAT
192.168.0.10:80

双向映射

入口 DNAT,返回方向根据连接跟踪自动完成相应的逆向转换;也可以组合显式 SNAT/DNAT 规则实现更复杂映射。

3. Netfilter、table、chain

iptables 是 Netfilter 的用户态配置工具之一。

经典表:

table 主要用途
filter 包过滤
nat NAT
mangle 修改报文属性
raw 连接跟踪前的原始处理

nat 表常见链:

chain 时机 常见动作
PREROUTING 路由判断前 DNAT
POSTROUTING 路由判断后 SNAT/MASQUERADE
OUTPUT 本机产生的包 本地 DNAT 等

4. SNAT / MASQUERADE 示例

动态出口地址:

1
2
3
iptables -t nat -A POSTROUTING \
-s 192.168.0.0/16 \
-j MASQUERADE

固定源地址:

1
2
3
iptables -t nat -A POSTROUTING \
-s 192.168.0.2 \
-j SNAT --to-source 100.100.100.100

5. DNAT 示例

1
2
3
4
iptables -t nat -A PREROUTING \
-d 100.100.100.100 \
-p tcp --dport 8080 \
-j DNAT --to-destination 192.168.0.2:80

作为路由/NAT 网关时还要开启转发:

1
sysctl -w net.ipv4.ip_forward=1

持久化时写入 sysctl 配置文件,而不是只依赖临时命令。

6. conntrack 为什么会成为 NAT 的性能核心

Linux 有状态 NAT 需要知道“这个响应包对应哪个原始请求”,所以会维护连接跟踪表。

一个连接通常用以下信息标识:

1
2
3
4
5
protocol
src ip
src port
dst ip
dst port

也就是常说的五元组。

连接跟踪的主要内核参数:

1
2
3
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max
sysctl net.netfilter.nf_conntrack_buckets

含义:

1
2
3
nf_conntrack_count    当前记录数
nf_conntrack_max 最大记录数
nf_conntrack_buckets 哈希表 bucket 数

7. nf_conntrack: table full 是非常直接的证据

如果 NAT 场景下并发突然下降、连接大量超时,先看内核日志:

1
dmesg | tail

如果出现:

1
nf_conntrack: table full, dropping packet

说明连接跟踪表已满,新的包会被丢弃。

可以临时调整:

1
sysctl -w net.netfilter.nf_conntrack_max=131072

但不能只会“把 max 调大”。conntrack 是内存哈希表,容量越大,内存消耗也越高;bucket 太少还会增加哈希冲突和链表查找成本。

资料中的一个估算思路为:

1
2
内存 ≈ nf_conntrack_max * 连接对象大小
+ nf_conntrack_buckets * bucket/链表项开销

具体对象大小与内核版本、架构有关,不应把示例值当成永久常量。

8. 直接看 conntrack 里都是什么连接

1
conntrack -L -o extended | head

统计总数:

1
conntrack -L -o extended | wc -l

统计 TCP 状态:

1
2
conntrack -L -o extended \
| awk '/tcp/ {sum[$6]++} END {for (i in sum) print i, sum[i]}'

如果大量记录处于 TIME_WAIT,可以继续看:

1
sysctl net.netfilter.nf_conntrack_tcp_timeout_time_wait

在短连接极多、且业务允许的情况下,可以评估适当缩短 conntrack 状态超时时间,但需要基于真实流量验证,而不是照抄固定数值。

9. NAT 性能排查的完整链路

资料中用 SystemTap 跟踪 kfree_skb(),发现大量丢包位于 nf_hook_slow,再用 perf 继续展开调用栈,最终看到:

1
2
3
ipv4_conntrack_in
br_nf_pre_routing
iptable_nat_ipv4_in

这个案例说明了一个非常重要的排障套路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
现象: NAT 后 QPS 暴跌

压测对比: host network 正常,NAT network 异常

确认丢包

跟踪丢包函数

perf 展开内核调用栈

发现 conntrack / bridge / DNAT 热点

检查 sysctl + dmesg

调整 conntrack 容量并复测

10. 当有状态 NAT 本身就是瓶颈

如果业务根本不需要动态状态跟踪,只做固定映射,可以考虑更轻量的无状态数据面。

极端高 PPS 环境下还可以考虑:

  • tc/eBPF;
  • XDP;
  • DPDK;
  • 专用硬件网关。

优化的根本原则是:不要为了实现一个简单的地址改写,承担不需要的状态维护成本。


十一、DDoS:从“网络性能异常”识别攻击流量

DDoS(Distributed Denial of Service)可以从三个层面耗尽资源:

  1. 带宽耗尽:链路被流量打满;
  2. 系统资源耗尽:CPU、内存、连接表、conntrack、Socket 队列被耗尽;
  3. 应用资源耗尽:恶意请求触发昂贵计算、数据库、DNS 查询等。

1. SYN Flood 的典型特征

攻击者持续发送 SYN,但不完成三次握手:

sequenceDiagram
    participant A as Source
    participant S as Server
    A->>S: SYN
    S-->>A: SYN + ACK
    Note over S: 等待最后 ACK,连接停留 SYN_RECV
    A->>S: SYN
    S-->>A: SYN + ACK

大量半连接会占满半连接队列。

2. 怎么从性能指标发现 SYN Flood

先看 PPS/BPS:

1
sar -n DEV 1

如果:

1
2
3
rxpck/s 极高
rxkB/s 并不高
平均包只有几十字节

说明很可能是小包洪泛。

抓包确认:

1
tcpdump -i eth0 -nn 'tcp dst port 80 and tcp[tcpflags] & tcp-syn != 0'

再看半连接:

1
ss -ant state syn-recv

或者传统方式:

1
netstat -n | grep SYN_RECV

3. Linux 内核层的缓解手段

查看半连接容量:

1
sysctl net.ipv4.tcp_max_syn_backlog

调整示例:

1
sysctl -w net.ipv4.tcp_max_syn_backlog=1024

减少 SYN+ACK 重试:

1
sysctl -w net.ipv4.tcp_synack_retries=1

开启 SYN Cookies:

1
sysctl -w net.ipv4.tcp_syncookies=1

SYN Cookies 的思想是:服务器收到 SYN 时不必立刻为它长期保存完整半连接状态,而是把连接信息编码进返回序列号;只有客户端真正返回合法 ACK 时,才恢复连接状态。

4. 为什么服务器内部只能“缓解” DDoS

如果恶意流量已经到达服务器:

  • 网卡已经收包;
  • PCIe/DMA 已经发生;
  • IRQ/softirq 已经消耗 CPU;
  • 协议栈至少已经处理了一部分。

即使最后在 iptables 丢掉,也已经付出了成本。

所以大规模 DDoS 需要分层防御:

1
2
3
4
5
6
7
8
9
运营商/高防/清洗中心

边界防火墙/ACL

XDP/DPDK/硬件数据面

Linux 内核

WAF/CDN/应用限流

如果攻击已经把机房公网带宽打满,服务器本机任何 sysctl 都无法“创造额外带宽”。必须在更上游丢弃流量。


十二、真正的网络优化:沿协议栈逐层做,不要先抄 sysctl

优化前先明确目标。

不同系统的“快”不是一回事:

  • NAT 网关:更关心 PPS 和接近线速转发;
  • 数据库、缓存:更关心低延迟;
  • Web 服务:需要兼顾 QPS、吞吐和延迟;
  • 大文件传输:更关心 BPS 与链路利用率;
  • 长连接网关:更关心连接数、内存和事件处理效率。

因此优化流程应该是:

flowchart LR
    A[明确业务目标] --> B[建立基准]
    B --> C[观察指标]
    C --> D[定位协议层]
    D --> E[只优化瓶颈层]
    E --> F[重新压测]
    F --> G{是否改善}
    G -- 是 --> H[固化配置并监控]
    G -- 否 --> C

1. 应用层:先减少不必要的网络工作

I/O 多路复用

高并发网络服务优先避免“一连接一线程”的同步阻塞模型,使用 epoll 等事件驱动模型。

合理的 worker 模型

选择:

  • master + workers;
  • event loop + worker pool;
  • SO_REUSEPORT 多监听 Socket。

核心目标是:让网络事件处理和业务 CPU 计算解耦,避免一个慢请求拖住事件循环。

长连接替代频繁短连接

短连接每次都要付出:

1
2
3
4
DNS(可能)
+ TCP handshake
+ TLS handshake(HTTPS)
+ TIME_WAIT/连接跟踪状态

如果业务允许,连接池和 Keep-Alive 往往比调一堆 TCP 参数更有效。

减少 payload

常见手段:

  • 二进制序列化;
  • 压缩;
  • 批量请求;
  • 合并小消息;
  • 避免重复传输不变化数据。

缓存

缓存既减少业务 CPU,也减少网络 I/O:

  • DNS 缓存;
  • 应用对象缓存;
  • CDN;
  • 静态资源缓存。

2. Socket 层:缓冲区不是越大越好

Socket 有读写缓冲区:

  • rmem 太小:高带宽、高 RTT 下可能限制吞吐;
  • wmem 太小:发送端容易阻塞;
  • 太大:每个连接占用更多内存,百万连接时会放大为巨大内存压力。

常见参数:

1
2
3
4
5
6
sysctl net.core.optmem_max
sysctl net.core.rmem_max
sysctl net.core.wmem_max
sysctl net.ipv4.tcp_rmem
sysctl net.ipv4.tcp_wmem
sysctl net.ipv4.udp_mem

TCP 自动调节参数通常是三个值:

1
min default max

一个重要估算是 BDP(Bandwidth-Delay Product,带宽时延积):

1
BDP = Bandwidth * RTT

如果链路是 10Gbps、RTT 是 20ms,要真正跑满链路,窗口/缓冲能力至少要覆盖相当数量的在途数据。

3. Socket 行为参数:TCP_NODELAYTCP_CORK

TCP_NODELAY

禁用 Nagle,适合低延迟、小消息场景。

程序级:

1
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, ...);

Nginx:

1
tcp_nodelay on;

TCP_CORK

暂时把小数据“塞住”,等待聚合成更大报文再发送,适合明确知道完整响应边界的场景。

它和 TCP_NODELAY 的目标相反,不能机械地两个都“开了试试”。

4. TCP:围绕连接生命周期优化

TCP 优化必须先理解:

  • 三次握手;
  • 四次挥手;
  • SYN_RECV;
  • ESTABLISHED;
  • FIN_WAIT;
  • CLOSE_WAIT;
  • TIME_WAIT;
  • 流量控制;
  • 拥塞控制;
  • Nagle;
  • Delayed ACK;
  • Keepalive。

大量 TIME_WAIT

先确认为什么会产生:通常是主动关闭连接的一方进入 TIME_WAIT。

更优先的应用层办法是:

  • 连接池;
  • HTTP Keep-Alive;
  • 减少无意义短连接。

系统层可以观察:

1
2
3
4
ss -ant state time-wait | wc -l
sysctl net.ipv4.tcp_max_tw_buckets
sysctl net.ipv4.ip_local_port_range
sysctl net.ipv4.tcp_tw_reuse

注意一个常见历史误区:

net.ipv4.tcp_fin_timeout 不是用来直接修改 TIME_WAIT 的 2MSL 时长,它主要影响 FIN_WAIT_2 等关闭阶段行为。不要把它当作“TIME_WAIT 秒数”照抄。

另一个历史参数:

1
net.ipv4.tcp_tw_recycle

因为 NAT 等环境下容易导致连接异常,早已被内核删除,不应再作为优化方案。

本地端口范围

1
sysctl net.ipv4.ip_local_port_range

客户端主动连接外部服务时,需要本地临时端口。微服务、代理、NAT 网关都可能因为大量主动连接而受到临时端口资源限制。

文件描述符

Socket 本质上也是文件描述符。

1
2
3
ulimit -n
sysctl fs.nr_open
sysctl fs.file-max

systemd 服务还需要检查:

1
2
[Service]
LimitNOFILE=1048576

不要只改 ulimit,却忘记服务实际由 systemd 启动。

SYN 队列

1
2
3
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.ipv4.tcp_syncookies
sysctl net.ipv4.tcp_synack_retries

这类参数主要用于连接突发与 SYN Flood 缓解。调整时要同时观察:

  • SYN_RECV;
  • accept queue;
  • 丢包;
  • listen overflow;
  • 应用 accept() 能力。

TCP Keepalive

1
2
3
sysctl net.ipv4.tcp_keepalive_time
sysctl net.ipv4.tcp_keepalive_intvl
sysctl net.ipv4.tcp_keepalive_probes

它适合发现“连接看起来还在,但对端实际上已经消失”的情况。

但业务层如果已经有自己的心跳协议,也要评估是否还需要过于激进的 TCP Keepalive,避免重复探测。

5. UDP:重点是缓冲区和包大小

UDP 没有连接状态和可靠重传逻辑,优化相对简单:

  • 调整 Socket/UDP 缓冲区;
  • 扩大本地端口范围;
  • 控制单个 datagram 大小;
  • 尽量避免 IP 分片;
  • 应用自己处理丢包、乱序、重试。

UDP “没有重传”并不代表没有性能问题,只是可靠性成本转移到了应用层。

6. 网络层:路由、转发、MTU、ICMP

作为路由器、NAT、容器宿主机时:

1
sysctl -w net.ipv4.ip_forward=1

反向路径校验:

1
sysctl net.ipv4.conf.eth0.rp_filter

它可以帮助过滤明显的源地址欺骗,但复杂多路径/策略路由环境需要谨慎评估。

MTU

1
2
ip link show eth0
ip link set dev eth0 mtu 1500

overlay 网络要保证整个路径 MTU 一致。如果底层仍是 1500,而 VXLAN 又增加额外头部,虚拟接口通常需要更小 MTU。

巨帧

在端到端所有设备都支持时,MTU 9000 一类 Jumbo Frame 可以降低同等吞吐量下的 PPS 和 CPU 开销。

前提是:整条链路一致支持。 中间任意设备不支持都可能产生难查的黑洞或分片问题。

7. 链路层和网卡:把每个 CPU 核真正用起来

高 PPS 场景下,单个 CPU 处理所有中断很容易成为瓶颈。

IRQ affinity / irqbalance

检查:

1
cat /proc/interrupts

可以通过 IRQ affinity 或 irqbalance 把网卡中断分散到多个 CPU。

RSS

RSS(Receive Side Scaling)利用网卡硬件多队列,把不同流分配到多个 RX queue,再由不同 CPU 处理。

RPS / RFS

当硬件 RSS 不足或需要更细调度时:

  • RPS:软件层把包分散到不同 CPU;
  • RFS:尽量让软中断处理 CPU 与应用消费该流的 CPU 靠近,提高 cache locality。

8. Offload:让网卡做它擅长的工作

Offload 作用
TSO 网卡完成 TCP 大包分段
GSO 在更靠近网卡的位置统一分段
UFO UDP 分片卸载(历史机制)
LRO 网卡接收侧合并多个 TCP 包
GRO 更通用的接收侧聚合
VXLAN Offload 网卡处理部分 VXLAN 封装/校验

查看:

1
ethtool -k eth0

不能简单认为“所有 offload 都应该打开”。例如转发、抓包、虚拟网络等场景下,某些 offload 会改变你看到的包形态,甚至与特定转发逻辑冲突。

9. 队列越大不一定越快

增大:

  • Ring Buffer;
  • qdisc 队列;
  • Socket Buffer;
  • backlog;

确实能减少突发丢包,但代价可能是排队时间上升。

换句话说:

1
2
3
吞吐量 ↑
并不保证
延迟 ↓

如果目标是实时交易、RPC、数据库,盲目加大缓冲区可能只是把“丢包”变成“排队 500ms”。

10. QoS 与 Traffic Control

当不同流量竞争同一接口时,可以使用 Linux Traffic Control:

1
tc qdisc show

通过 qdisc/class/filter 做:

  • 限速;
  • 优先级;
  • 流量整形;
  • 模拟延迟/丢包;
  • QoS。

这属于“管理竞争关系”,不是凭空提高链路带宽。


十三、网络工具应该怎么记:按“指标”而不是按命令背

1. 从指标找工具

想看什么 工具
接口配置/状态 ipifconfig
BPS/PPS sar/proc/net/dev
进程流量 nethogs
IP/连接流量 iftop
Socket/连接状态 ssnetstat
网卡速率/Offload ethtool
RTT pinghping3
路由路径 traceroutemtrip route
DNS dignslookup
抓包 tcpdump、Wireshark
NAT/防火墙 iptables
conntrack conntrack
CPU/内核热点 perf
动态内核跟踪 SystemTap、bcc/eBPF 工具

2. 从工具反推问题

ss

最适合快速确认:

  • 连接数;
  • TCP 状态;
  • Recv-Q / Send-Q;
  • 监听端口;
  • 进程。

sar

最适合快速确认:

  • PPS;
  • BPS;
  • 接口错误;
  • TCP/UDP/ICMP 统计。

tcpdump

最适合回答:

“这个包到底有没有发出去?对端到底有没有回?”

Wireshark

最适合回答:

“这条 TCP 流的交互顺序是什么?哪里出现重传、延迟 ACK、窗口异常或连接关闭?”

perf / eBPF / SystemTap

最适合回答:

“包进入内核后 CPU 到底花在哪里?”


十四、几个特别容易混淆的知识点

1. 服务器连接数并不等于 65535

TCP/UDP 端口是 16 位,但一条连接并不是只用“服务器端口”标识,而是由五元组区分:

1
protocol + src_ip + src_port + dst_ip + dst_port

因此服务器可以在同一个 :80 上同时服务海量客户端:

1
2
3
10.0.0.1:50001 -> 192.168.0.10:80
10.0.0.2:50001 -> 192.168.0.10:80
10.0.0.3:50001 -> 192.168.0.10:80

它们是不同连接。

对主动发起连接的一方,本地临时端口确实是重要资源,但更精确地说,限制与源 IP、目标 IP/端口组合及 ip_local_port_range 有关,而不是整台机器永久只能有 65535 条 TCP 连接。

2. sk_buff 不是 Socket 接收缓冲区

sk_buff 是内核描述网络包的数据结构;Socket Buffer 是面向某个 Socket 的收发队列。两者处于不同抽象层次。

3. Bandwidth、BPS、PPS 不能互换

1
2
3
Bandwidth = 链路能力
BPS = 实际字节/比特吞吐
PPS = 包处理能力

64B 小包和 1500B 大包,即使 BPS 一样,CPU 成本可能完全不同。

4. ping 通不代表 TCP 一定通,ping 不通也不代表服务一定挂了

ICMP 可能被禁。

真正排业务端口时可以使用:

1
2
hping3 -c 3 -S -p 443 example.com
traceroute --tcp -p 443 example.com

5. Recv-Q/Send-Q 的含义依赖 Socket 状态

ESTABLISHED 和 LISTEN 的含义不同。排障前先看 State,不要脱离状态解释队列值。

6. TIME_WAIT 多不一定是故障

大量短连接业务天然会产生很多 TIME_WAIT。正确问题应该是:

  • 是否耗尽临时端口?
  • 是否耗尽 conntrack?
  • 是否导致内存异常?
  • 是否因为短连接设计本身不合理?

而不是“看到 TIME_WAIT 就把 timeout 调成 1 秒”。

7. NAT 不等于安全边界

NAT 隐藏了内部地址,并不代表应用自动安全。只要端口映射、转发或返回路径允许访问,应用漏洞仍然存在。


十五、一套可直接用于线上排障的网络性能流程

阶段 1:确认现象

先回答:

1
2
3
4
5
6
是连接不上?
还是连接慢?
还是请求慢?
还是吞吐不够?
还是偶发超时?
还是只有高并发时慢?

如果连问题类型都没定义清楚,后面所有调优都容易跑偏。

阶段 2:看基础接口

1
2
3
4
ip -s addr
ip route
ethtool eth0
sar -n DEV 1

确认:

  • 链路;
  • MTU;
  • errors/drop;
  • BPS/PPS;
  • 路由。

阶段 3:看 TCP/Socket

1
2
3
4
ss -s
ss -tanp
ss -ltnp
netstat -s

关注:

1
2
3
4
5
6
7
8
SYN_RECV
ESTABLISHED
TIME_WAIT
CLOSE_WAIT
Recv-Q
Send-Q
retransmit
reset

阶段 4:确认 DNS 和路径 RTT

1
2
3
time dig example.com
ping -n -c 3 example.com
traceroute --tcp -p 443 -n example.com

如果 IP 访问快、域名访问慢,优先排 DNS。

阶段 5:抓包

1
tcpdump -i eth0 -nn host <peer-ip> -w issue.pcap

用 Wireshark 看:

  • 握手耗时;
  • 重传;
  • Dup ACK;
  • RST;
  • Zero Window;
  • Delayed ACK;
  • 请求到首字节时间;
  • FIN 过程。

阶段 6:怀疑 NAT/防火墙时看 conntrack

1
2
3
4
conntrack -L | head
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max
dmesg | grep -i conntrack

阶段 7:怀疑内核 CPU 时继续下钻

1
2
3
perf top
perf record -a -g -- sleep 30
perf report

然后再根据热点决定是否需要:

  • IRQ affinity;
  • RSS/RPS/RFS;
  • Offload;
  • conntrack 调整;
  • Socket buffer;
  • XDP/DPDK。

阶段 8:任何优化都必须回到基准测试

优化前:

1
2
3
4
5
QPS = X
P99 = Y
PPS = Z
softirq = A
retrans = B

优化后重新用同样流量、同样机器、同样测试窗口测一次。

如果没有数据对比,“感觉快了”不算性能优化。


十六、最终应形成的网络性能心智模型

把全文压缩成一条链路,就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
应用协议

I/O 模型 / worker 模型

Socket 收发缓冲区

TCP / UDP

IP / 路由 / MTU / NAT / conntrack

Netfilter / qdisc

softirq / CPU

网卡队列 / RSS / Offload

物理链路

对应的分析原则也可以压缩成一句话:

先用指标确定是哪一层,再用工具找到证据,最后只优化真正的瓶颈。

Linux 网络性能优化最危险的做法,是从网上复制一份“万能 sysctl.conf”,一次性改几十个参数。网络参数之间存在大量依赖和权衡:缓冲区变大可能提高吞吐却增加延迟,Nagle 和 Delayed ACK 可能相互放大等待,conntrack 增大可以容纳更多状态却会消耗更多内存,Offload 能降低 CPU 也可能改变转发和抓包行为。

真正稳定的优化过程应该是:

1
2
3
4
5
6
7
8
理解协议栈
-> 建立基准
-> 观察指标
-> 抓取证据
-> 定位层级
-> 修改一个关键变量
-> 重新压测
-> 监控长期效果

做到这一点,DNS 超时、Socket 堆积、TCP 重传、NAT table full、SYN Flood、40ms 神秘延迟、软中断跑满、网卡队列丢包,就不再是互不相关的一堆“Linux 网络玄学”,而只是同一条数据路径上发生在不同位置的性能问题。


Linux 网络性能全景:从协议栈、C10K 到 NAT、抓包与内核调优
https://allendericdalexander.github.io/2026/08/11/devops/linux/performanceOptimization/05linux-network-performance-analysis-and-optimization/
作者
AtLuoFu
发布于
2026年8月11日
许可协议