网络是Linux系统最核心的功能之一,网络是一种把不同计算机或网络设备连接到一起的技术,它本质上是一种进程间通信方式,特别是跨系统的进程间通信,必须要通过网络才能进行。随着高并发、分布式、云计算、微服务等技术的普及,网络的性能也变得越来越重要;

那么,Linux网络又是怎么工作的呢?又有哪些指标衡量网络的性能呢?

网络性能篇

Linux网络基础原理

网络模型

**网络模型:**7层网络模型(OSI 网络模型)与4层网络模型(TCP/IP 网络模型)

OSI是一个分层结构,共有7层,从下往上分别是:物理层、数据链路层(通常简称链路层)、网络层、传输层、会话层、表示层和应用层,如下图所示。其中各个功能层执行特定的、相对简单的任务。每一层都由上一层支配,并从上一层接收数据,为上一层提供服务。

image-20230824121731132

第1层至第3层主要是完成数据交换和数据传输,称为网络低层,即通信子网;第5层至第7层主要是完成信息处理服务的功能,称为网络高层;低层与高层之间由第4层衔接。通常也将会话层、表示层和应用层统称为应用层,将传输层及以下各层统称为数据传输层。

但是OSI模型还是太复杂了,也没能提供一个可实现的方法。因此,在Linux中,我们实际上使用的是另一个更实用的四层模型,也就是TCP/IP模型;

image-20230824122122724

  • 网络接口层

网络接口层(Network Interface Layer)又称网络访问层(Network Access Layer),包括OSI的物理层和链路层,负责向网络物理介质发送数据包,从网络物理介质接收数据包。TCP/IP并没有对物理层和链路层进行定义,它只是支持现有的各种底层网络技术和标准。网络接口层涉及操作系统中的设备驱动程序和网络接口设备。

  • 网络层

网络层又称为互联网层或IP层,负责处理IP数据包的传输、路由选择、流量控制和拥塞控制。

TCP/IP网络层的底部是负责Internet地址(IP地址)与底层物理网络地址之间进行转换的地址解析协议(Address Resolution Protocol, ARP)和反向地址解析协议(Reverse Address Resolution Protocol, RARP)。

ARP用于根据IP地址获取物理地址。RARP用于根据物理地址查找其IP地址。由于ARP和RARP用于完成网络层地址和链路层地址之间的转换,也有人将ARP和RARP作为链路层协议。

IP协议(Internet Protocol)既是网络层的核心协议,也是TCP/IP协议簇中的核心协议。网络互联的基本功能主要是由IP协议来完成的。Internet控制报文协议(Internet Control Message Protocol, ICMP)是主机和网关进行差错报告、控制和进行请求/应答的协议。Internet组管理协议(Internet Group Management Protocol, IGMP)用于实现组播中的组成员管理。

  • 传输层

传输层为两台主机上的应用程序提供端到端的通信。

TCP/IP的传输层包含传输控制协议TCP (Transmission Control Protocol)和用户数据报协议UDP(User Datagram Protocol)。这两种协议对应两类不同性质的服务,TCP为主机提供可靠的面向连接的传输服务;UDP为应用层提供简单高效的无连接传输服务。上层的应用进程可以根据可靠性要求或效率要求决定是使用TCP还是UDP来提供服务。

  • 应用层

这个层次包括OSI的会话层、表示层和应用层,直接为特定的应用提供服务。应用层为用户提供一些常用的应用程序。TCP/IP给出了应用层的一些常用协议规范,如文件传输协议FTP、简单邮件传输协议SMTP、超文本传输协议HTTP等。

同时上图也给出对应层的关系, 可以形象地理解TCP/IP和OSI模型的关系;

虽然说Linux实际按照TCP/IP模型, 实现了网络协议栈,但是在平时交流中还是习惯的用OSI七层模型来描述,例如,在说到七层和四层的负载均衡,对应的分别是OSI模型中的应用层个传输层(而它们对应到TCP/IP模型中,实际上是四层和三层);

Linux网络栈

有了TCP/IP模型后,在进行网络传输时,数据包就会按照协议栈,对上一层发来的数据进行逐层处理;然后封装上该层的协议头,再发送给下一层;

当然,网络包在每一层的处理逻辑,都取决于各层采用的网络协议。比如在应用层,一个提供REST API的应用,可以使用HTTP协议,把它需要传输的JSON数据封装到HTTP协议中,然后向下传递到下一层(也就是TCP层);

而封装做的事情就很简单了,只是在原来的负载前后,增加固定的格式的元数据,原始的负载数据并不会被修改;

比如,以通过TCP协议通信的网络包为例,以下面这个图,我们可以看到,应用程序数据在每个层的封装格式;

image-20230824130640838

其中:

  • 传输层在应用程序数据前面增加了TCP头;
  • 网络层在TCP数据包前增加了IP头;
  • 而网络接口层,又在IP数据包前后分别增加了帧头和帧尾。

这些新增的头部和尾部,都按照特定的协议格式填充;

这些新增的头部和尾部,增加了网络包的大小,但我们都知道,物理链路并不能传输任意大小的数据包。网络接口配置的最大传输单元(MTU),就规定了最大的IP包大小;

在我们最常见的以太网中,MTU默认值时1500(这也是Linux的默认值);

一旦网络包超过MTU的大小,就会在网络层分片,以保证分片后的IP包不大于MTU值。显然,MTU越大,需要的分包也就越少,自然,网络的吞吐能力就越好。

理解了TCP/IP网络模型和网络包的封装原理后,你就很容易想到,Linux内核中的网络栈,其实也类似于TCP/IP的四层结构。如下图所示,就是Linux通用网络栈的示意图;

image-20230824131848421

从上到下来看这个网络栈,可以发现:

  • 最上层的应用程序,需要通过系统调用,来和套接字接口进行交互;
  • 套接字的下面,就是前面提到的传输层、网络层和网络接口层;
  • 最底层,就是网卡驱动程序以及物理网卡设备;

网卡是发送和接收网络包的基本设备,在系统的启动过程中,网卡通过内核中的网卡驱动程序注册到系统中。而在网络收发过程中,内核通过中断跟网卡进行交互;

结合到前面提到的Linux网络栈,可以看出,网络包的处理非常复杂。所以网卡硬中断只处理最核心的网卡数据读取或者发送,而协议栈中的大部分逻辑,都会放到软中断中处理;

Linux网络收发流程

了解Linux网络栈后,再来看看,Linux到底是怎么收发网络包的?

网络包的接收流程

1
https://segmentfault.com/a/1190000008836467
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
1.当一个网络帧到达网卡后,网卡会通过 DMA 方式,把这个网络包放到收包队列中;然后通过硬中断,告诉中断处理程序已经收到了网络包。

2.接着,网卡中断处理程序会为网络帧分配内核数据结构(sk_buff),并将其拷贝到 sk_buff 缓冲区中;然后再通过软中断,通知内核收到了新的网络帧。

3.接下来,内核协议栈从缓冲区中取出网络帧,并通过网络协议栈,从下到上逐层处理这个网络帧。比如,
    a.在链路层检查报文的合法性,找出上层协议的类型(比如 IPv4 还是 IPv6),再去掉帧头、帧尾,然后交给网络层。
    b.网络层取出 IP 头,判断网络包下一步的走向,比如是交给上层处理还是转发。当网络层确认这个包是要发送到本机后,就会取出上层协议的类型(比如 TCP 还是 UDP),去掉 IP 头,再交给传输层处理。
    c.传输层取出 TCP 头或者 UDP 头后,根据 < 源 IP、源端口、目的 IP、目的端口 > 四元组作为标识,找出对应的 Socket,并把数据拷贝到 Socket 的接收缓存中。

4.最后,应用程序就可以使用 Socket 接口,读取到新接收到的数据了。

网络包的发送流程

https://segmentfault.com/a/1190000008926093

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
了解网络包的接收流程后,就很容易理解网络包的发送流程。网络包的发送流程就是上图的右半部分,很容易发现,网络包的发送方向,正好跟接收方向相反。

1.首先,应用程序调用 Socket API(比如 sendmsg)发送网络包。

2.由于这是一个系统调用,所以会陷入到内核态的套接字层中。套接字层会把数据包放到 Socket 发送缓冲区中。

3.接下来,网络协议栈从 Socket 发送缓冲区中,取出数据包;再按照 TCP/IP 栈,从上到下逐层处理。比如,传输层和网络层,分别为其增加 TCP 头和 IP 头,执行路由查找确认下一跳的 IP,并按照 MTU 大小进行分片。

4.分片后的网络包,再送到网络接口层,进行物理地址寻址,以找到下一跳的 MAC 地址。然后添加帧头和帧尾,放到发包队列中。这一切完成后,会有软中断通知驱动程序:发包队列中有新的网络帧需要发送。

5.最后,驱动程序通过 DMA ,从发包队列中读出网络帧,并通过物理网卡把它发送出去。

Linux网络性能

了解了Linux网络的基本原理和收发流程后,我们应该如何去观察网络的性能情况。具体而言,哪些指标又是可以用来衡量Linux的网络性能呢?

性能指标

性能指标:带宽、吞吐量、延时、PPS、网络的可用性(网络能否正常通信)、并发连接数(TCP 连接数量)、丢包率(丢包百分比)、重传率(重新传输的网络包比例)

  • 带宽,表示链路的最大传输速率,单位通常为 b/s (比特 / 秒)。
  • 吞吐量,表示单位时间内成功传输的数据量,单位通常为 b/s(比特 / 秒)或者 B/s(字节 / 秒)。吞吐量受带宽限制,而吞吐量 / 带宽,也就是该网络的使用率。
  • 延时,表示从网络请求发出后,一直到收到远端响应,所需要的时间延迟。在不同场景中,这一指标可能会有不同含义。比如,它可以表示,建立连接需要的时间(比如 TCP 握手延时),或一个数据包往返所需的时间(比如 RTT)。
  • PPS,是 Packet Per Second(包 / 秒)的缩写,表示以网络包为单位的传输速率。PPS 通常用来评估网络的转发能力,比如硬件交换机,通常可以达到线性转发(即 PPS 可以达到或者接近理论最大值)。而基于 Linux 服务器的转发,则容易受网络包大小的影响。

除了这些指标,网络的可用性(网络能否正常通信)、并发连接数(TCP 连接数量)、丢包率(丢包百分比)、重传率(重新传输的网络包比例)等也是常用的性能指标。

网络配置

分析网络问题的第一步,通常是查看网络接口的配置和状态。可以使用ifconfig或者ip命令,来查看网络的配置;个人更推荐使用ip工具,因为它提供了更丰富的功能和更易用的接口;

ifconfig eth0

ip -s addr show dev eth0

ifconfigip分别属于软件包net-tools和iproute2, iproute2是net-tools的下一代。通常情况下它们会在发行版中默认安装。如果你找不到ifconfig或者ip命令, 可以安装这两个软件包;

以网络接口eth0为例,可以运行下面的两个命令,查看它的配置和状态:

image-20230824140009973

  • 第一,网络接口的状态标志。ifconfig 输出中的 RUNNING ,或 ip 输出中的 LOWER_UP ,都表示物理网络是连通的,即网卡已经连接到了交换机或者路由器中。如果你看不到它们,通常表示网线被拔掉了。
  • 第二,MTU 的大小。MTU 默认大小是 1500,根据网络架构的不同(比如是否使用了 VXLAN 等叠加网络),你可能需要调大或者调小 MTU 的数值。
  • 第三,网络接口的 IP 地址、子网以及 MAC 地址。这些都是保障网络功能正常工作所必需的,你需要确保配置正确。
  • 第四,网络收发的字节数、包数、错误数以及丢包情况,特别是 TX 和 RX 部分的 errors、dropped、overruns、carrier 以及 collisions 等指标不为 0 时,通常表示出现了网络 I/O 问题。其中:
    • errors 表示发生错误的数据包数,比如校验错误、帧同步错误等;
    • dropped 表示丢弃的数据包数,即数据包已经收到了 Ring Buffer,但因为内存不足等原因丢包;
    • overruns 表示超限数据包数,即网络 I/O 速度过快,导致 Ring Buffer 中的数据包来不及处理(队列满)而导致的丢包;
    • carrier 表示发生 carrirer 错误的数据包数,比如双工模式不匹配、物理电缆出现问题等;
    • collisions 表示碰撞数据包数。

套接字信息

ifconfig和ip只显示了网络接口收发数据包的统计信息,但在实际的性能问题中,网络协议栈中的统计信息,我们也需要关注。可以用netstat或者ss, 来查看套接字、网络栈、网络接口以及路由表的信息;

个人更推荐,使用ss来查询网络的连接信息,因此它比netstat提供了更好的性能(速度更快);

套接字信息:netstat -nlp、ss -ltnp

-l 表示只显示前面3行

-n 表示显示数字地址和端口(而不是名字)

-p 表示显示进程信息

image-20230824180049085

协议栈统计信息:netstat -s、ss -s

-l 表示只显示监听套接字

-t 表示只显示TCP套接字

-n 表示显示数字地址和端口(而不是名字)

-p 表示显示进程信息

image-20230824180255933

netstat和ss的输出也是类似的,都显示了套接字的状态、接收队列、发送队列、本地地址、远端地址、进程PID和进程名称等。

其中,接收队列(Recv-Q)和发送队列(Send-Q)需要你特别关注,它们通常应该是 0。当你发现它们不是 0 时,说明有网络包的堆积发生。也要注意到在不同套接字状态下,它们的含义不同。

当套接字处于连接状态(Established)时,

  • Recv-Q 表示套接字缓冲还没有被应用程序取走的字节数(即接收队列长度)。
  • 而 Send-Q 表示还没有被远端主机确认的字节数(即发送队列长度)。

当套接字处于监听状态(Listening)时,

  • Recv-Q 表示syn backlog的当前值。
  • 而 Send-Q 表示最大的syn backlog值。

而syn backlog是TCP协议栈中的半连接队列长度,相应的也有一个全连接队列(accept queue), 它们都是维护TCP状态的重要机制;

顾名思义,所谓半连接,就是还没有完成TCP三次握手的连接,连接只进行了一半,而服务器收到了客户端的SYN包后,就会把这个连接放到半连接队列中,然后再向客户端发送SYN+ACK包;

而全连接,则是服务器收到了客户端的ACK,完成了TCP三次握手,然后就会把这个连接挪到全连接队列中。这些全连接中的套接字,还需要被accept()系统调用取走,这样,服务器就可以真正处理客户端的请求了;


协议栈统计信息

类似地,使用netstat或ss, 也可以查看协议栈的信息;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@VM-8-14-centos ~]# netstat -s
Ip:
    482804230 total packets received
    98200740 forwarded
    332 with unknown protocol
    0 incoming packets discarded
    384325372 incoming packets delivered
    485443266 requests sent out
    61 dropped because of missing route
    555356 reassemblies required
    277678 packets reassembled ok
    277674 fragments received ok
    555348 fragments created
...
Tcp:
    28707645 active connections openings
    2763438 passive connection openings
    7638992 failed connection attempts
    710616 connection resets received
    8 connections established
    332069919 segments received
    335344757 segments send out
    4687402 segments retransmited
    9004 bad segments received.
    41401912 resets sent
    InCsumErrors: 8742

image-20230824181910815

这些协议栈的统计信息都很直观, ss 只显示已经连接、关闭、孤儿套接字等简要统计,而 netstat 则提供的是更详细的网络协议栈信息, 比如上面的netstat的输出示例, 就展示了TCP协议的主动连接、被动连接、失败重试、发送和接收的分段数量等各种信息。

套接字信息:netstat -nlp、ss -ltnp;协议栈统计信息:netstat -s、ss -s

网络吞吐和PPS

接下来,我们来看看如何查看系统当前的网络吞吐量和PPS,在这里,使用之前使用过的sar。

给sar增加-n参数就可以查看网络的统计信息,比如网络接口(DEV), 网络接口错误(EDEV), TCP、UDP、ICMP等等。执行下面的命令,就可以得到网络接口的统计信息:

1
2
# 数字1表示每隔1秒输出一组数据
# sar -n DEV 1

image-20230824182823260

这儿输出的指标比较多,来简单解释下它们的含义:

  • rxpck/s 和 txpck/s 分别是接收和发送的 PPS,单位为包 / 秒。
  • rxkB/s 和 txkB/s 分别是接收和发送的吞吐量,单位是 KB/ 秒。
  • rxcmp/s 和 txcmp/s 分别是接收和发送的压缩数据包数,单位是包 / 秒。
  • %ifutil 是网络接口的使用率,即半双工模式下为 (rxkB/s+txkB/s)/Bandwidth,而全双工模式下为 max(rxkB/s, txkB/s)/Bandwidth。

其中,Bandwidth 可以用 ethtool 来查询,它的单位通常是 Gb/s 或者 Mb/s,不过注意这里小写字母 b ,表示比特而不是字节。我们通常提到的千兆网卡、万兆网卡等,单位也都是比特。

image-20230824184049904

连通性和延时

通常使用ping, 来测试远程主机的联通性和延时,而这是基于ICMP协议。比如,执行下面的命令,可以测试到本机到120.229.79.95这个IP地址的连通性和延时:

1
#-c3 表示发送三次ICMP包后停止

image-20230824184613488

ping的输出,可以分为两部分;

  • 第一部分,是每个ICMP请求的信息,包括ICMP序列号(icmp_seq)、TTL(生存时间,跳数)以及往返延时;
  • 第二部分,则是三次ICMP请求的汇总;