「Linux Performance」- 网络性能指标

TCP 三次握手原理,你真的理解吗?
关于TCP 半连接队列和全连接队列

nslookup-OK-but-ping-fail

网络性能指标(Linux)

带宽,表示链路的最大传输速率,单位通常为 b/s (比特/秒)
通常带宽受物理网卡的限制,网卡确定后,带宽随即确定(当然,实际带宽会受限于整个网络链路中最小的那个模块)

吞吐量,表示单位时间内成功传输的数据量,单位通常为 b/s(比特/秒)或者 B/s(字节/秒)
吞吐量受带宽限制。“网络带宽测试”,这里测试的实际上不是带宽,而是网络吞吐量。网络使用率 = 吞吐量 / 带宽
Linux 服务器的网络吞吐量一般会比带宽小,而对交换机等专门的网络设备来说,吞吐量一般会接近带宽。

延时,表示从网络请求发出后,一直到收到远端响应,所需要的时间延迟
在不同场景中,延时可能会有不同含义。比如它可以表示,建立连接需要的时间(比如 TCP 握手延时),或一个数据包往返所需的时间(比如 RTT)。

PPS,是 Packet Per Second(包/秒)的缩写,表示以网络包为单位的传输速率
PPS 通常用来评估网络的转发能力,比如硬件交换机通常可以达到线性转发(即 PPS 可以达到或者接近理论最大值)。而基于 Linux 服务器的转发,则容易受网络包大小的影响。
对 TCP 或者 Web 服务来说,更多会用并发连接数和每秒请求数(QPS,Query per Second)等指标,它们更能反应实际应用程序的性能。

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

网络基准测试

在 Linux 中,不同协议层的行为显然不同。测试之前应该弄清楚被测协议所属的协议层:
1)基于 HTTP 或者 HTTPS 的 Web 应用程序,显然属于应用层,需要我们测试 HTTP/HTTPS 的性能
2)对大多数游戏服务器,需要我们测试 TCP/UDP 的性能
3)把 Linux 作为软交换机或路由器来用,我们更关注网络包的处理能力(即 PPS),重点关注网络层的转发性能

转发性能(网络接口层和网络层)

它们主要负责网络包的封装、寻址、路由以及发送和接收。在这两个网络协议层中,每秒可处理的网络包数(PPS)是最重要的性能指标。尤其是小包的处理能力。

我们这里使用 pktgen 是 Linux 内核自带的高性能网络测试工具。需要加载 pktgen 内核模块后,再通过 /proc 文件系统来交互:

# modprobe pktgen

# ps -ef | grep pktgen | grep -v grep
root     26384     2  0 06:17 ?        00:00:00 [kpktgend_0]
root     26385     2  0 06:17 ?        00:00:00 [kpktgend_1]

// pktgen 在每个 CPU 上启动一个内核线程

# ls /proc/net/pktgen/
kpktgend_0  kpktgend_1  pgctrl

// 通过 /proc/net/pktgen 下面的同名文件,跟这些线程交互

// pgctrl 则主要用来控制这次测试的开启和停止

定义并执行测试:

#!/bin/sh

# 定义一个工具函数,方便后面配置各种测试选项
function pgset() {
    local result
    echo $1 > $PGDEV

    result=`cat $PGDEV | fgrep "Result: OK:"`
    if [ "$result" = "" ]; then
         cat $PGDEV | fgrep Result:
    fi
}

# 为 0 号线程绑定 enp0s25 网卡
PGDEV=/proc/net/pktgen/kpktgend_0
pgset "rem_device_all"   # 清空网卡绑定
pgset "add_device enp0s25"  # 添加eth0网卡

# 配置eth0网卡的测试选项
PGDEV=/proc/net/pktgen/enp0s25
pgset "count 1000000"    # 总发包数量
pgset "delay 5000"       # 不同包之间的发送延迟(单位纳秒)
pgset "clone_skb 0"      # SKB包复制
pgset "pkt_size 64"      # 网络包大小
pgset "dst 10.10.50.188" # 目的IP
# pgset "dst_mac 11:11:11:11:11:11"  # 目的MAC

# 启动测试
PGDEV=/proc/net/pktgen/pgctrl
pgset "start"

查看测试结果:

# cat /proc/net/pktgen/enp0s25
Params: count 1000000  min_pkt_size: 64  max_pkt_size: 64
     frags: 0  delay: 5000  clone_skb: 0  ifname: enp0s25
     flows: 0 flowlen: 0
     queue_map_min: 0  queue_map_max: 0
     dst_min: 10.10.50.188  dst_max:
     src_min:   src_max:
     src_mac: 54:ee:75:19:f1:bd dst_mac: 00:00:00:00:00:00
     udp_src_min: 9  udp_src_max: 9  udp_dst_min: 9  udp_dst_max: 9
     src_mac_count: 0  dst_mac_count: 0
     Flags:
Current:
     pkts-sofar: 1000000  errors: 0
     started: 125329380781us  stopped: 125336668320us idle: 7175us
     seq_num: 1000001  cur_dst_mac_offset: 0  cur_src_mac_offset: 0
     cur_saddr: 10.10.12.122  cur_daddr: 10.10.50.188
     cur_udp_dst: 9  cur_udp_src: 9
     cur_queue_map: 0
     flows: 0
Result: OK: 7287538(c7280363+d7175) usec, 1000000 (64byte,0frags)
  137220pps 70Mb/sec (70256640bps) errors: 0


// Params 是测试选项
// Current 是测试进度
// Result 是测试结果,包含测试所用时间、网络包数量(分片)、PPS、吞吐量、错误数

我们发现 PPS 为 13 万,吞吐量为 70 Mb/s,没有发生错误。那么 13 万的 PPS 到底是好还是坏呢?

我们可以对比千兆交换机,1000Mbps / ((64 + 20) * 8) = 1.5Mpps,比我们测试得到的 12 万大很多。但是不用担心,现在的多核服务器和万兆网卡已经很普遍,稍做优化就可以达到数百万的 PPS。而且如果使用 DPDK 或 XDP,还能达到千万数量级。

TCP/UDP 性能

使用 iperf 和 netperf 测试,我们使用 iperf3 测试。

// 在远程主机上,执行如下命令

# iperf3 -s -i 1 -p 10000 // -s表示启动服务端,-i表示汇报间隔,-p表示监听端口

// 在本地主机上,执行如下命令

# iperf3 -c 10.10.50.188 -b 1G -t 15 -P 2 -p 10000
...
[  5]  13.00-14.00  sec  5.25 MBytes  44.0 Mbits/sec    0   96.2 KBytes
[  7]  13.00-14.00  sec  5.75 MBytes  48.2 Mbits/sec    0    115 KBytes
[SUM]  13.00-14.00  sec  11.0 MBytes  92.3 Mbits/sec    0
- - - - - - - - - - - - - - - - - - - - - - - - -
[  5]  14.00-15.00  sec  5.00 MBytes  41.9 Mbits/sec    0   96.2 KBytes
[  7]  14.00-15.00  sec  6.00 MBytes  50.3 Mbits/sec    0    115 KBytes
[SUM]  14.00-15.00  sec  11.0 MBytes  92.3 Mbits/sec    0
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-15.00  sec  81.4 MBytes  45.5 Mbits/sec    0             sender
[  5]   0.00-15.00  sec  81.0 MBytes  45.3 Mbits/sec                  receiver
[  7]   0.00-15.00  sec  86.4 MBytes  48.3 Mbits/sec    0             sender
[  7]   0.00-15.00  sec  85.9 MBytes  48.1 Mbits/sec                  receiver
[SUM]   0.00-15.00  sec   168 MBytes  93.8 Mbits/sec    0             sender
[SUM]   0.00-15.00  sec   167 MBytes  93.4 Mbits/sec                  receiver
...

// -c 表示启动客户端,10.10.50.188 为目标服务器的地址,-b 表示目标带宽(单位是bits/s),-t 表示测试时间,-P 表示并发数,-p 表示目标服务器监听端口

// 这台机器 TCP 接收的带宽(吞吐量)为 93.8 Mbits/sec,跟目标的 1Gb/s 相比,还是有些差距的
// 因为我们用的是百兆设备,应该以 100M 作为目标带宽

HTTP 性能

要测试 HTTP 的性能,也有大量的工具可以使用,比如 ab、webbench 等。我们使用 ab 进行测试。

# ab -c 1000 -n 10000 http://10.10.50.188/ // -c 表示并发请求数为 1000,-n 表示总的请求数为 10000
...
Server Software:        nginx/1.16.1
Server Hostname:        10.10.50.188
Server Port:            80

Document Path:          /
Document Length:        4833 bytes

Concurrency Level:      1000
Time taken for tests:   6.942 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      50680000 bytes
HTML transferred:       48330000 bytes
Requests per second:    1440.55 [#/sec] (mean)
Time per request:       694.178 [ms] (mean)
Time per request:       0.694 [ms] (mean, across all concurrent requests)
Transfer rate:          7129.61 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1  151 365.2     72    4263
Processing:     4  290 519.0     74    5829
Waiting:        4  182 425.2     73    5807
Total:         20  441 704.7    147    6906

Percentage of the requests served within a certain time (ms)
  50%    147
  66%    335
  75%    478
  80%    512
  90%    979
  95%   1527
  98%   2866
  99%   4045
 100%   6906 (longest request)

// Requests per second 为 1440.55
// Time per request,第一行 694.178 包括了线程运行的调度时间和网络请求响应时间,下一行 0.694 表示实际请求的响应时间
// Transfer rate 表示吞吐量(BPS)为 7129.61 KB/s

// 连接时间汇总部分,则是分别展示了建立连接、请求、等待、汇总等的各类时间,包括最小、最大、平均、中值处理时间。

// 最后的请求延迟汇总部分,则给出了不同时间段内处理请求的百分比,比如, 90% 的请求,都可以在 979ms 内完成。

应用负载性能

使用 ab 工具,可以得到某个页面的访问性能,但这个结果跟用户的实际请求,很可能不一致。因为用户请求往往会附带着各种各种的负载(payload),而这些负载会影响 Web 应用程序内部的处理逻辑,从而影响最终性能。可以用 wrk、TCPCopy、Jmeter、LoadRunner 等实现这个目标。我们这里使用 wrk 测试。

以 wrk 为例,它是个 HTTP 性能测试工具,内置了 LuaJIT,方便你根据实际需求,生成所需的请求负载,或者自定义响应的处理方法。

# wrk -c 1000 -t 2 http://10.10.50.188 // 这里使用 2 个线程、并发 1000 连接
Running 10s test @ http://10.10.50.188
  2 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   380.88ms  343.71ms   2.00s    84.51%
    Req/Sec     1.11k   141.45     1.51k    76.50%
  22177 requests in 10.05s, 108.92MB read
  Socket errors: connect 0, read 0, write 0, timeout 418
Requests/sec:   2205.70
Transfer/sec:     10.83MB

// Requests/sec:   2205.70
// Transfer/sec:     10.83MB
// Latency  Avg 380.88ms

// 比前面 ab 的测试结果要好很多。性能工具本身的性能,对性能测试也是至关重要的

使用 Lua 脚本可以用来实现复杂场景的性能测试。它会一次调用以下函数:
1)setup(thread),每个线程执行一次
2)init(args),每个线程执行一次
3)delay(),每次请求调用
4)request(),每次请求调用
5)response(status, headers, body),每次请求调用
6)done(summary, latency, requests),整个过程执行一次

可以在 setup 阶段,为请求设置认证参数:

-- example script that demonstrates response handling and
-- retrieving an authentication token to set on all future
-- requests

token = nil
path  = "/authenticate"

request = function()
   return wrk.format("GET", path)
end

response = function(status, headers, body)
   if not token and status == 200 then
      token = headers["X-Token"]
      path  = "/resource"
      wrk.headers["X-Token"] = token
   end
end

在执行测试时,通过 -s 选项,执行脚本的路径:

# wrk -c 1000 -t 2 -s auth.lua http://10.10.50.188

像 Jmeter 或者 LoadRunner(商业产品),则针对复杂场景提供了脚本录制、回放、GUI 等更丰富的功能,使用起来也更加方便。

查看网络信息

查看网口配置和状态

# ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
      inet 10.240.0.30 netmask 255.240.0.0 broadcast 10.255.255.255
      inet6 fe80::20d:3aff:fe07:cf2a prefixlen 64 scopeid 0x20<link>
      ether 78:0d:3a:07:cf:3a txqueuelen 1000 (Ethernet)
      RX packets 40809142 bytes 9542369803 (9.5 GB)
      RX errors 0 dropped 0 overruns 0 frame 0
      TX packets 32637401 bytes 4815573306 (4.8 GB)
      TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
​
# ip -s addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
  link/ether 78:0d:3a:07:cf:3a brd ff:ff:ff:ff:ff:ff
  inet 10.240.0.30/12 brd 10.255.255.255 scope global eth0
      valid_lft forever preferred_lft forever
  inet6 fe80::20d:3aff:fe07:cf2a/64 scope link
      valid_lft forever preferred_lft forever
  RX: bytes packets errors dropped overrun mcast
   9542432350 40809397 0       0       0       193
  TX: bytes packets errors dropped carrier collsns
   4815625265 32637658 0       0       0       0

网络接口的状态标志:ifconfig 输出中的 RUNNING,ip 输出中的 LOWER_UP,表示物理网络是连通的,即网卡已经连接到交换机或者路由器。否则表示没有正常连接

MTU 的大小:默认大小 1500,根据网络架构的不同(比如是否使用了 VXLAN 等叠加网络),你可能需要调大或者调小 MTU 的数值

网络接口的 IP 地址、子网、MAC 地址:这些都是保障网络功能正常工作所必需的,你需要确保配置正确

网络收发的字节数、包数、错误数以及丢包情况:特别是 TX 和 RX 的 errors、dropped、overruns、carrier、collisions 不为 0 时,通常表示出现网络 I/O 问题:
1)errors 表示发生错误的数据包数,比如校验错误、帧同步错误等
2)dropped 表示丢弃的数据包数,即数据包已经收到了 Ring Buffer,但因为内存不足等原因丢包;
3)overruns 表示超限数据包数,即网络 I/O 速度过快,导致 Ring Buffer 中的数据包来不及处理(队列满)而导致的丢包;
4)carrier 表示发生 carrirer 错误的数据包数,比如双工模式不匹配、物理电缆出现问题等
5)collisions 表示碰撞数据包数

获取套接字信息

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

# head -n 3 表示只显示前面3行
# -l 表示只显示监听套接字
# -n 表示显示数字地址和端口(而不是名字)
# -p 表示显示进程信息
$ netstat -nlp | head -n 3
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      840/systemd-resolve

# -l 表示只显示监听套接字
# -t 表示只显示 TCP 套接字
# -n 表示显示数字地址和端口(而不是名字)
# -p 表示显示进程信息
$ ss -ltnp | head -n 3
State    Recv-Q    Send-Q        Local Address:Port        Peer Address:Port
LISTEN   0         128           127.0.0.53%lo:53               0.0.0.0:*        users:(("systemd-resolve",pid=840,fd=13))
LISTEN   0         128                 0.0.0.0:22               0.0.0.0:*        users:(("sshd",pid=1459,fd=3))

接收队列(Recv-Q)和发送队列(Send-Q)需要特别关注,通常应该是 0。当它们不为 0 时,说明有网络包的堆积发生。当然还要注意,在不同套接字状态下,它们的含义不同:
当套接字处于连接状态(Established)时:Recv-Q 表示套接字缓冲还没有被应用程序取走的字节数;Send-Q 表示还没有被远端主机确认的字节数;
当套接字处于监听状态(Listening)时:Recv-Q 表示全连接队列的长度;Send-Q 表示全连接队列的最大长度;

全连接,是指 Server 收到 Client 的 ACK(完成 TCP 三次握手),然后就会把这个连接放到全连接队列中。这些全连接中的套接字,还需要被 accept() 系统调用取走,Server 才可以开始真正处理客户端的请求。

半连接,是指还没有完成 TCP 三次握手的连接,连接只进行了一半。Server 收到 Client 的 SYN 包后,就会把这个连接放到半连接队列中,然后再向 Client 发送 SYN+ACK 包。

协议栈统计信息

使用 netstat 或 ss 可以查看协议栈的信息:

# netstat -s
...
Tcp:
    3244906 active connection openings
    23143 passive connection openings
    115732 failed connection attempts
    2964 connection resets received
    1 connections established
    13025010 segments received
    17606946 segments sent out
    44438 segments retransmitted
    42 bad segments received
    5315 resets sent
    InCsumErrors: 42
...

# ss -s
Total: 186 (kernel 1446)
TCP:   4 (estab 1, closed 0, orphaned 0, synrecv 0, timewait 0/0), ports 0

Transport Total     IP        IPv6
   1446      -         -
RAW    2         1         1
UDP    2         2         0
TCP    4         3         1
...

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

查看网络吞吐和 PPS 信息

命令 sar -n 可以查看网络的统计信息,比如网络接口(DEV)、网络接口错误(EDEV)、TCP、UDP、ICMP 等等

# sar -n DEV 1 // 数字1表示每隔1秒输出一组数据
Linux 4.15.0-1035-azure (ubuntu)   01/06/19   _x86_64_  (2 CPU)

13:21:40        IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s   %ifutil
13:21:41         eth0     18.00     20.00      5.79      4.25      0.00      0.00      0.00      0.00
13:21:41      docker0      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
13:21:41           lo      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00

// 第三、四列:rxpck/s 和 txpck/s 分别表示每秒接收、发送的网络帧数,也就是 PPS
// 第五、六列:rxkB/s 和 txkB/s 分别表示每秒接收、发送的千字节数,也就是 BPS
// rxcmp/s 和 txcmp/s 分别是接收和发送的压缩数据包数,单位是包 / 秒
// %ifutil 是网络接口的使用率,即半双工模式下为 (rxkB/s+txkB/s)/Bandwidth,而全双工模式下为 max(rxkB/s, txkB/s)/Bandwidth(Bandwidth 用 ethtool 查询,注意单位)

查看连通性和延时

通常使用 ping ,来测试远程主机的连通性和延时,基于 ICMP 协议

# ping -c3 114.114.114.114 // -c3 表示发送三次ICMP包后停止
PING 114.114.114.114 (114.114.114.114) 56(84) bytes of data.
64 bytes from 114.114.114.114: icmp_seq=1 ttl=54 time=244 ms
64 bytes from 114.114.114.114: icmp_seq=2 ttl=47 time=244 ms
64 bytes from 114.114.114.114: icmp_seq=3 ttl=67 time=244 ms

--- 114.114.114.114 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 244.023/244.070/244.105/0.034 ms

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

网络问题排查收集

Linux 系统排查 4——网络篇
Linux network troubleshooting and debugging
How to show failed ping?

通用的办法:
从 Linux 的网络栈中跟踪数据包的发送,以检测数据包是否发送成功;

已知问题:
1)TCP dies on a Linux laptop

参考文献

33 | 关于 Linux 网络,你必须知道这些(上)
36 | 套路篇:怎么评估系统的网络性能?