前面我们一块探讨了在CPU、内存、磁盘等方面的Linux性能问题,以及相应的性能分析和优化方法。下面我们继续聊聊Linux网络性能,这一块内容与前面的密切相关,还未阅读前面内容的可以在文章末尾有列表跳转查看。
当你在 Linux 上执行网络数据交互时,一个请求会从终端飞到远端服务器上。如果你是搞运维的,可能你每天都在用 ss、ping、curl,偶尔也会碰到「网络慢」「连不上」「丢包」等等这些常见问题。但是可能说不清楚,数据包从你的进程出发,到飞出网卡,中间到底走了哪几步。
这篇文章不打算分析具体的原理,也不打算详解 OSI 七层模型,这些在网络上都能够查到更详细的。这篇文章主要讲一下:一个数据包在 Linux 里是怎么从无到有、从上到下、最后跑出去的,以及出问题的时候,你应该盯着哪几个指标数字。
数据包的一生:从进程到网卡
第一步:系统调用
当执行数据包发送时,进程会先调用 send()。就这么简单的一个函数,背后是一次用户态到内核态的切换。
现代操作系统不允许用户程序直接操作硬件。你的数据必须先离开用户空间,进入内核空间,才能继续往下传。这个边界就是系统调用。它不是一个轻量操作,每次 syscall 都有几百纳秒到几微秒的开销。高并发下,这个成本会被放大。
数据进入内核后,被包装成一个 sk_buff(Socket Buffer)。这是 Linux 网络栈的通用容器。从 TCP 段到 IP 包,从 RX 到 TX,几乎所有网络路径都围着 sk_buff 转。内核不移动数据本身,它只操纵 sk_buff 的指针。
第二步:传输层
sk_buff 进入传输层。
如果是 TCP,内核查 socket 状态、算 sequence number、标记 flags(SYN/ACK/FIN),加上 TCP 头部。数据太大就分片(segment)。
如果是 UDP,那简单得多。加个头部就丢给下层。UDP 不做可靠保证,所以快。代价是丢了不重传,应用层自己兜着。
这之后,sk_buff 被标注为一个完整的数据包(源 IP、目标 IP、源端口、目标端口、协议),这时就有了身份。
第三步:网络层,通过路由做转发
IP 层查路由表(ip route 看到的东西),决定包从哪个接口出去、下一跳是谁。路由背后是 FIB(Forwarding Information Base),简单理解就是:内核有张地图,send() 的时候它翻地图找出口。
目标 IP 和本机在同子网,直接发。不在,丢给默认网关。
IP 层还会检查包大小是否超过 MTU(通常 1500 字节)。超了就分片。大包拆成小包,各自带上 IP 头,到了对端再组装起来。
第四步:数据链路层
IP 层锁定了下一跳,但物理链路不认识 IP。它只认 MAC 地址。
内核查 ARP 表(ip neigh 查看)。缓存里有,直接写 MAC。没有,就广播一个 ARP 请求:"谁是 192.168.1.1?请报告你的 MAC。"
拿到 MAC 地址后,包被封装成以太网帧,准备交给网卡。
第五步:网卡驱动
以太网帧被放进 Ring Buffer(环形缓冲区)。Ring Buffer 是内核和网卡之间的共享内存区域,网卡通过 DMA(直接内存访问)从 Ring Buffer 里拿走数据,不需要 CPU 参与。
到这一步,CPU 才终于解放。前面的路由、封装、拷贝全是 CPU 在干。但数据进了 Ring Buffer,网卡硬件直接通过 DMA 拿走,CPU 可以去忙别的了。
这也是高并发下常见的瓶颈。Ring Buffer 太小,网卡消费不过来,队列满了就开始丢包。ethtool -g eth0 可以看当前大小和最大上限。
在虚拟网络,特别是容器时代
目前云计算、容器使用已经非常普遍,只管物理机的日子已经过去了。容器和虚拟机在 Linux 的网络栈上又叠了几层抽象。
- veth pair
- bridge(网桥),虚拟交换机。Docker 默认搭一个
docker0,容器全接上去。 - Network Namespace,每个容器拥有独立网络栈,自己的网卡、路由表、iptables。彼此隔离。
当你在容器里 curl,数据包从容器 eth0 出发,穿过 veth 到达宿主机网桥,然后查路由、NAT 转换(容器通常是私有 IP),最后从宿主机的物理网卡出去,进入上面那五步。
每一层都有开销,也都是排查时的盲区。
网络出问题经常要查的指标
下面这几个指标覆盖了 80% 的问题。
带宽是理论上限。1Gbps、10Gbps、100Gbps,网卡规格的上限。
吞吐量才是实际传输速率。受限于 TCP 窗口、延迟、丢包。可以用 iperf 测试一下。带宽 10Gbps 但吞吐只有 2Gbps,瓶颈在协议栈或上层,不在网卡。
延迟,ping 测的就是它。延迟 = 传播延迟(光速限制)+ 处理延迟(设备处理时间)+ 排队延迟(缓冲区排队)。高延迟不一定致命,高延迟还丢包才是核心问题所在。
丢包率,用 mtr 比 ping 更精准。丢包不一定是网络不通。Ring Buffer 满了、conntrack 满了、iptables 拦了,都会丢。用 ethtool -S eth0 看硬件级丢弃,跟应用层丢包区分开。
Connection Tracking 表是最被经常忽视的。每个连接内核都跟踪状态。conntrack 表默认 65536 或 262144 条记录,满了新连接直接丢弃。conntrack -S 看使用率。高并发下偶发性断连,查这里大概率能找到根因。
Socket 缓冲区大小由 tcp_rmem 和 tcp_wmem 决定。太小,发送端被限流,接收端来不及收。太大,内存占用飙升。sysctl net.ipv4.tcp_rmem 查看,典型值是 4096 131072 6291456(最小/默认/最大)。
TCP 重传率是最直接的网络抖动信号。nstat -az TcpExtTCPSynRetrans TcpExtTCPRetransCount 看重传计数。超过 1% 说明链路不太干净了。可能拥塞,可能不稳定,可能中间设备丢包。
常用的几个命令
ss看 socket,也可以用 netstat,只是早期的命令没有ss好用ip路由和接口管理,也可以用 ifconfig 和 routetc流量控制,查队列和带宽限制,这个属于 iproute2 套件的一部分ethtool
它们覆盖了四个层面:应用层连接(ss)、网络层路由(ip)、链路层队列(tc)、物理层硬件(ethtool)。