性能指标回顾
第一,带宽,表示链路的最大传输速率,单位是 b/s(比特 / 秒)。在你为服务器选购网卡时,带宽就是最核心的参考指标。常用的带宽有 1000M、10G、40G、100G 等。
第二,吞吐量,表示没有丢包时的最大数据传输速率,单位通常为 b/s (比特 / 秒)或者 B/s(字节 / 秒)。吞吐量受带宽的限制,吞吐量 / 带宽也就是该网络链路的使用率。
第三,延时,表示从网络请求发出后,一直到收到远端响应,所需要的时间延迟。这个指标在不同场景中可能会有不同的含义。它可以表示建立连接需要的时间(比如 TCP 握手延时),或者一个数据包往返所需时间(比如 RTT)。
最后,PPS,是 Packet Per Second(包 / 秒)的缩写,表示以网络包为单位的传输速率。PPS 通常用来评估网络的转发能力,而基于 Linux 服务器的转发,很容易受到网络包大小的影响(交换机通常不会受到太大影响,即交换机可以线性转发)
你可能在很多地方听说过“网络带宽测试”,这里测试的实际上不是带宽,而是网络吞吐量。Linux 服务器的网络吞吐量一般会比带宽小,而对交换机等专门的网络设备来说,吞吐量一般会接近带宽。
什么时候用PPS?什么时候用QPS?
PPS,则是以网络包为单位的网络传输速率,通常用在需要大量转发的场景中。
对 TCP 或者 Web 服务来说,更多会用并发连接数和每秒请求数(QPS,Query per Second)等指标,它们更能反应实际应用程序的性能。
网络基准测试
评估网络性能应该弄清楚应用程序属于协议栈的哪一层?
- 基于 HTTP 或者 HTTPS 的 Web 应用程序,显然属于应用层,需要我们测试 HTTP/HTTPS 的性能;
- 而对大多数游戏服务器来说,为了支持更大的同时在线人数,通常会基于 TCP 或 UDP ,与客户端进行交互,这时就需要我们测试 TCP/UDP 的性能;
- 当然,还有一些场景,是把 Linux 作为一个软交换机或者路由器来用的。这种情况下,你更关注网络包的处理能力(即 PPS),重点关注网络层的转发性能。
各协议层的性能测试
网络包转发性能
网络接口层和网络层,它们主要负责网络包的封装、寻址、路由以及发送和接收。在这两个网络协议层中,每秒可处理的网络包数 PPS,就是最重要的性能指标。特别是 64B 小包的处理能力,值得我们特别关注。那么,如何来测试网络包的处理能力呢?
测试网络包处理能力的性能工具
- hping3
hping3 作为一个 SYN 攻击的工具来使用。实际上, hping3 更多的用途,是作为一个测试网络包处理能力的性能工具。
- pktgen
inux 内核自带的高性能网络测试工具 pktgen。pktgen 支持丰富的自定义选项,方便你根据实际需要构造所需网络包。从而更准确地测试出目标服务器的性能。
不过,在 Linux 系统中,你并不能直接找到 pktgen 命令。因为 pktgen 作为一个内核线程来运行,需要你加载 pktgen 内核模块后,再通过 /proc 文件系统来交互。下面就是 pktgen 启动的两个内核线程和 /proc 文件系统的交互文件:
[root@node100 ~]# modprobe pktgen [root@node100 ~]# [root@node100 ~]# ps -ef | grep pktgen | grep -v grep root 3910 2 0 14:25 ? 00:00:00 [kpktgend_0] root 3911 2 0 14:25 ? 00:00:00 [kpktgend_1] [root@node100 ~]# [root@node100 ~]# ls /proc/net/pktgen/ kpktgend_0 kpktgend_1 pgctrl [root@node100 ~]#
pktgen 在每个 CPU 上启动一个内核线程,并可以通过 /proc/net/pktgen 下面的同名文件,跟这些线程交互;而 pgctrl 则主要用来控制这次测试的开启和停止。
在使用 pktgen 测试网络性能时,需要先给每个内核线程 kpktgend_X 以及测试网卡,配置 pktgen 选项,然后再通过 pgctrl 启动测试。
测试案例
以发包测试为例,假设发包机器使用的网卡是 eth0,而目标机器的 IP 地址为 192.168.0.30,MAC 地址为 11:11:11:11:11:11。
测试脚本
#!/bin/bash # 定义一个工具函数,方便后面配置各种测试选项 function pgset() { local result echo $1 > $PGDEV result=`cat $PGDEV | fgrep "Result: OK:"` if [ "$result" = "" ]; then cat $PGDEV | fgrep Result: fi } # 为 0 号线程绑定 eth0 网卡 PGDEV=/proc/net/pktgen/kpktgend_0 pgset "rem_device_all" # 清空网卡绑定 pgset "add_device eth0" # 添加 eth0 网卡 # 配置 eth0 网卡的测试选项 PGDEV=/proc/net/pktgen/eth0 pgset "count 1000000" # 总发包数量 pgset "delay 5000" # 不同包之间的发送延迟 (单位纳秒) pgset "clone_skb 0" # SKB 包复制 pgset "pkt_size 64" # 网络包大小 pgset "dst 192.168.1.10" # 目的 IP pgset "dst_mac 51:54:10:4d:77:**" # 目的 MAC ip a查看 # 启动测试 PGDEV=/proc/net/pktgen/pgctrl pgset "start"
结果可以从 /proc 文件系统中获取。通过下面代码段中的内容,我们可以查看刚才的测试报告:
[root@node100 nettest]# cat /proc/net/pktgen/eth1 Params: count 1000000 min_pkt_size: 64 max_pkt_size: 64 frags: 0 delay: 5000 clone_skb: 0 ifname: eth1 flows: 0 flowlen: 0 queue_map_min: 0 queue_map_max: 0 dst_min: 192.168.1.10 dst_max: src_min: src_max: src_mac: 08:00:27:45:19:a9 dst_mac: 08:00:27:0b:87:e3 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: 23701084929us stopped: 23724280164us idle: 10146us seq_num: 1000001 cur_dst_mac_offset: 0 cur_src_mac_offset: 0 cur_saddr: 192.168.1.9 cur_daddr: 192.168.1.10 cur_udp_dst: 9 cur_udp_src: 9 cur_queue_map: 0 flows: 0 Result: OK: 23195234(c23185088+d10146) usec, 1000000 (64byte,0frags) 43112pps 22Mb/sec (22073344bps) errors: 0
测试报告主要分为三个部分:
- 第一部分的 Params 是测试选项;
- 第二部分的 Current 是测试进度,其中, packts so far(pkts-sofar)表示已经发送了 100 万个包,也就表明测试已完成。
- 第三部分的 Result 是测试结果,包含测试所用时间、网络包数量和分片、PPS、吞吐量以及错误数。
PPS 为 4万,吞吐量为 22 Mb/s,没有发生错误。那么,4 万的 PPS 好不好呢?
作为对比,你可以计算一下千兆交换机的 PPS。交换机可以达到线速(满负载时,无差错转发),它的 PPS 就是 1000Mbit 除以以太网帧的大小,即 1000Mbps/((64+20)*8bit) = 1.5 Mpps(其中,20B 为以太网帧前导和帧间距的大小)。
你看,即使是千兆交换机的 PPS,也可以达到 150 万 PPS,比我们测试得到的 4万大多了。所以,看到这个数值你并不用担心,现在的多核服务器和万兆网卡已经很普遍了,稍做优化就可以达到数百万的 PPS。而且,如果你用了上节课讲到的 DPDK 或 XDP ,还能达到千万数量级。
TCP/UDP 性能
说到 TCP 和 UDP 的测试,我想你已经很熟悉了,甚至可能一下子就能想到相应的测试工具,比如 iperf 或者 netperf。
特别是现在的云计算时代,在你刚拿到一批虚拟机时,首先要做的,应该就是用 iperf ,测试一下网络性能是否符合预期。
iperf 和 netperf 都是最常用的网络性能测试工具,测试 TCP 和 UDP 的吞吐量。它们都以客户端和服务器通信的方式,测试一段时间内的平均吞吐量。
测试案例
在目标机器上启动 iperf 服务端
# -s 表示启动服务端,-i 表示汇报间隔,-p 表示监听端口 $ iperf3 -s -i 1 -p 10000
iperf 的最新版本为 iperf3
另一台机器上运行 iperf 客户端,运行测试,运行期间cpu软中断(si)飙升。
# -c 表示启动客户端,192.168.1.9 为目标服务器的 IP # -b 表示目标带宽 (单位是 bits/s) # -t 表示测试时间 # -P 表示并发数,-p 表示目标服务器监听端口 vm]# iperf3 -c 192.168.1.9 -b 1G -t 15 -P 2 -p 10000
回到目标服务器,查看 iperf 的报告:
[ ID] Interval Transfer Bandwidth [ 5] 0.00-15.03 sec 0.00 Bytes 0.00 bits/sec sender [ 5] 0.00-15.03 sec 1.73 GBytes 991 Mbits/sec receiver [ 7] 0.00-15.03 sec 0.00 Bytes 0.00 bits/sec sender [ 7] 0.00-15.03 sec 1.39 GBytes 793 Mbits/sec receiver [SUM] 0.00-15.03 sec 0.00 Bytes 0.00 bits/sec sender [SUM] 0.00-15.03 sec 3.12 GBytes 1.78 Gbits/sec receiver
这台机器 TCP 接收的带宽(吞吐量)为 1.78Gb/s, 我的机器带宽应该是1Gb/s. 为啥还会超过呢?
HTTP 性能
要测试 HTTP 的性能,也有大量的工具可以使用,比如 ab、webbench 等,都是常用的 HTTP 压力测试工具。其中,ab 是 Apache 自带的 HTTP 压测工具,主要测试 HTTP 服务的每秒请求数、请求延迟、吞吐量以及请求延迟的分布情况等。
服务端
使用 Docker 启动一个 Nginx 服务
docker run -p 80:80 -itd nginx
客户端
如果超时缩小测试参数。
-c 表示并发请求数,-n 表示总的请求数 [root@node101 opt]# ab -c 100 -n 5000 http://192.168.1.9:80/ This is ApacheBench, Version 2.3 <$Revision: 1430300 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 192.168.1.9 (be patient) Completed 500 requests Completed 1000 requests Completed 1500 requests Completed 2000 requests Completed 2500 requests Completed 3000 requests Completed 3500 requests Completed 4000 requests Completed 4500 requests Completed 5000 requests Finished 5000 requests Server Software: nginx/1.21.5 Server Hostname: 192.168.1.9 Server Port: 80 Document Path: / Document Length: 615 bytes Concurrency Level: 100 Time taken for tests: 2.507 seconds Complete requests: 5000 Failed requests: 0 Write errors: 0 Total transferred: 4240000 bytes HTML transferred: 3075000 bytes Requests per second: 1994.38 [#/sec] (mean) Time per request: 50.141 [ms] (mean) Time per request: 0.501 [ms] (mean, across all concurrent requests) Transfer rate: 1651.60 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 1 18 5.0 18 38 Processing: 5 31 16.0 27 115 Waiting: 2 24 14.8 21 100 Total: 20 49 16.8 45 128 Percentage of the requests served within a certain time (ms) 50% 45 66% 51 75% 56 80% 60 90% 69 95% 79 98% 96 99% 119 100% 128 (longest request)
ab 的测试结果分为三个部分,分别是请求汇总、连接时间汇总还有请求延迟汇总。以上面的结果为例,我们具体来看。
在请求汇总部分,你可以看到:
- Requests per second 为 1994;
- 每个请求的延迟(Time per request)分为两行,第一行的 50ms 表示平均延迟,包括了线程运行的调度时间和网络请求响应时间,而下一行的 0.501ms ,则表示实际请求的响应时间;
- Transfer rate 表示吞吐量(BPS)为 1651KB/s。
连接时间汇总部分,则是分别展示了建立连接、请求、等待以及汇总等的各类时间,包括最小、最大、平均以及中值处理时间。
最后的请求延迟汇总部分,则给出了不同时间段内处理请求的百分比,比如, 90% 的请求,都可以在 69ms内完成。
应用负载性能
当你用 iperf 或者 ab 等测试工具,得到 TCP、HTTP 等的性能数据后,这些数据是否就能表示应用程序的实际性能呢?我想,你的答案应该是否定的。
比如,你的应用程序基于 HTTP 协议,为最终用户提供一个 Web 服务。这时,使用 ab 工具,可以得到某个页面的访问性能,但这个结果跟用户的实际请求,很可能不一致。因为用户请求往往会附带着各种各种的负载(payload),而这些负载会影响 Web 应用程序内部的处理逻辑,从而影响最终性能。
那么,为了得到应用程序的实际性能,就要求性能工具本身可以模拟用户的请求负载,而 iperf、ab 这类工具就无能为力了。幸运的是,我们还可以用 wrk、TCPCopy、Jmeter 或者 LoadRunner 等实现这个目标。
以 wrk 为例,它是一个 HTTP 性能测试工具,内置了 LuaJIT,方便你根据实际需求,生成所需的请求负载,或者自定义响应的处理方法。
[root@node101 bin]# /usr/local/bin/wrk -c 1000 -t 2 http://192.168.1.9 Running 10s test @ http://192.168.1.9 2 threads and 1000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 151.08ms 113.62ms 732.37ms 79.64% Req/Sec 2.91k 712.25 5.08k 74.84% 55161 requests in 10.05s, 44.87MB read Requests/sec: 5488.74 Transfer/sec: 4.47MB
这里使用 2 个线程、并发 1000 连接,重新测试了 Nginx 的性能。你可以看到,每秒请求数为 5488,吞吐量为4.4MB,平均延迟为 151ms,比前面 ab 的测试结果要好很多。
这也说明,性能工具本身的性能,对性能测试也是至关重要的。不合适的性能工具,并不能准确测出应用程序的最佳性能。
当然,wrk 最大的优势,是其内置的 LuaJIT,可以用来实现复杂场景的性能测试。wrk 在调用 Lua 脚本时,可以将 HTTP 请求分为三个阶段,即 setup、running、done,如下图所示:
可以在 setup 阶段,为请求设置认证参数(来自于 wrk 官方示例):
-- 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
测试结果
[root@node101 opt]# /usr/local/bin/wrk -c 1000 -s auth.lua -t 2 http://192.168.1.9 Running 10s test @ http://192.168.1.9 2 threads and 1000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 122.01ms 63.73ms 339.23ms 67.60% Req/Sec 3.27k 551.23 5.27k 73.78% 63553 requests in 10.07s, 18.67MB read Non-2xx or 3xx responses: 63553 Requests/sec: 6309.74 Transfer/sec: 1.85MB
wrk 需要你用 Lua 脚本,来构造请求负载。这对于大部分场景来说,可能已经足够了 。不过,它的缺点也正是,所有东西都需要代码来构造,并且工具本身不提供 GUI 环境。
像 Jmeter 或者 LoadRunner(商业产品),则针对复杂场景提供了脚本录制、回放、GUI 等更丰富的功能,使用起来也更加方便。
小结
今天,我带你一起回顾了网络的性能指标,并学习了网络性能的评估方法。
性能评估是优化网络性能的前提,只有在你发现网络性能瓶颈时,才需要进行网络性能优化。根据 TCP/IP 协议栈的原理,不同协议层关注的性能重点不完全一样,也就对应不同的性能测试方法。比如,
在应用层,你可以使用 wrk、Jmeter 等模拟用户的负载,测试应用程序的每秒请求数、处理延迟、错误数等;
而在传输层,则可以使用 iperf 等工具,测试 TCP 的吞吐情况;
再向下,你还可以用 Linux 内核自带的 pktgen ,测试服务器的 PPS。
由于低层协议是高层协议的基础。所以,一般情况下,我们需要从上到下,对每个协议层进行性能测试,然后根据性能测试的结果,结合 Linux 网络协议栈的原理,找出导致性能瓶颈的根源,进而优化网络性能。