36-套路篇:怎么评估系统的网络性能?
网络性能指标
实际上通常用带宽、吞吐量、延时、PPS(Packet Per Second)等指标衡量网络的性能
- 带宽,表示链路的最大传输速率,单位通常为b/s(比特/秒)
- 吞吐量,表示单位时间内成功传输的数据量,单位通常为b/s(比特/秒)或者B/s(字节/秒)
吞吐量受带宽限制,而吞吐量/带宽,也就是该网络的使用率 - 延时,表示从网络请求发出后,一直到收到远端响应,所需要的时间延迟
在不同场景中,这一指标可能会有不同含义
比如它可以表示,建立连接需要的时间(比如TCP握手延时),或一个数据包往返所需的时间(比如RTT) - PPS,是Packet Per Second(包/秒)的缩写,表示以网络包为单位的传输速率
PPS通常用来评估网络的转发能力
比如硬件交换机,通常可以达到线性转发(即 PPS 可以达到或者接近理论最大值)
而基于Linux服务器的转发,则容易受网络包大小的影响
除了这些指标,网络的可用性(网络能否正常通信)、并发连接数(TCP连接数量)
丢包率(丢包百分比)、重传率(重新传输的网络包比例)等也是常用的性能指标
网络基准测试
Linux网络基于TCP/IP协议栈,而不同协议层的行为显然不同
那么测试之前应该弄清楚要评估的网络性能,究竟属于协议栈的哪一层?
换句话说应用程序基于协议栈的哪一层呢?
根据前面学过的 TCP/IP 协议栈的原理
- 基于HTTP或者HTTPS的Web应用程序,显然属于应用层,需要测试HTTP/HTTPS的性能
- 对大多数游戏服务器来说,为了支持更大的同时在线人数
通常会基于TCP或UDP 与客户端进行交互,这时就需要我们测试TCP/UDP的性能 - 把Linux作为一个软交换机或者路由器来用的
这种情况下关注网络包的处理能力(即 PPS),重点关注网络层的转发性能
各协议层的性能测试
转发性能
网络接口层和网络层,它们主要负责网络包的封装、寻址、路由以及发送和接收
在这两个网络协议层中,每秒可处理的网络包数PPS,就是最重要的性能指标
特别是64B小包的处理能力,值得特别关注
那么如何来测试网络包的处理能力呢?
Linux内核自带的高性能网络测试工具pktgen
pktgen支持丰富的自定义选项,方便根据实际需要构造所需网络包,从而更准确地测试出目标服务器的性能
不过在Linux系统中,并不能直接找到pktgen命令
因为pktgen作为一个内核线程来运行,需要加载pktgen内核模块后,再通过/proc文件系统来交互
下面就是pktgen启动的两个内核线程和/proc文件系统的交互文件
# 系统版本
root@alnk:~# lsb_release -a
....
Description:Ubuntu 18.04.5 LTS
Release:18.04
root@alnk:~# uname -a
Linux alnk 4.15.0-136-generic #140-Ubuntu SMP Thu Jan 28 05:20:47 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
root@alnk:~# cat /proc/version
Linux version 4.15.0-136-generic (buildd@lcy01-amd64-029) (gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)) #140-Ubuntu SMP Thu Jan 28 05:20:47 UTC 2021
##
root@alnk:~# modprobe pktgen
root@alnk:~# ps -ef|grep pktgen|grep -=vv grep
root 3524 2 0 09:58 ? 00:00:00 [kpktgend_0]
root 3526 2 0 09:58 ? 00:00:00 [kpktgend_1]
root@alnk:~# ls /proc/net/pkgttgen/
kpktgend_0 kpktgend_1 pgctrl
pktgen在每个CPU上启动一个内核线程,并可以通过/proc/net/pktgen下面的同名文件,跟这些线程交互
而pgctrl则主要用来控制这次测试的开启和停止
如果modprobe命令执行失败,说明内核没有配置CONFIG_NET_PKTGEN选项
这就需要配置pktgen内核模块(即CONFIG_NET_PKTGEN=m)后,重新编译内核才可以使用
在使用pktgen测试网络性能时,需要先给每个内核线程kpktgend_X以及测试网卡,配置 pktgen 选项
然后再通过pgctrl启动测试
以发包测试为例,假设发包机器使用的网卡是eth0
而目标机器的IP地址为192.168.0.55,MAC地址为 11:11:11:11:11:11
接下来就是一个发包测试的示例
root@alnk:~# cat psget.sh
# 定义一个工具函数,方便后面配置各种测试选项
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.0.55" # 目的IP
pgset "dst_mac 11:11:11:11:11:11" # 目的MAC
# 启动测试
PGDEV=/proc/net/pktgen/pgctrl
pgset "start"
##### 启动执行
root@alnk:~# chmod +x psget.sh
root@alnk:~# ./psget.sh
稍等测试完成后,结果可以从/proc文件系统中获取
通过下面代码段中的内容可以查看刚才的测试报告
root@alnk:~# cat /proc/net/pktgen/eth0
Params: count 1000000 min_pkt_size: 64 max_pkt_size: 64
frags: 0 delay: 5000 clone_skb: 0 ifname: eth0
flows: 0 flowlen: 0
queue_map_min: 0 queue_map_max: 0
dst_min: 192.168.0.55 dst_max:
src_min: src_max:
src_mac: fa:16:3e:26:00:b6 dst_mac: 11:11:11:11:11:11
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: 15520168046us stopped: 15529403173us idle: 7838854us
seq_num: 1000001 cur_dst_mac_offset: 0 cur_src_mac_offset: 0
cur_saddr: 192.168.0.53 cur_daddr: 192.168.0.55
cur_udp_dst: 9 cur_udp_src: 9
cur_queue_map: 0
flows: 0
Result: OK: 9235127(c1396272+d7838854) usec, 1000000 (64byte,0frags)
108282pps 55Mb/sec (55440384bps) errors: 0
##
1. 第一部分的Params是测试选项
2. 第二部分的Current是测试进度
其中packts so far(pkts-sofar)表示已经发送了100万个包,也就表明测试已完成
3. 第三部分的Result是测试结果,包含测试所用时间、网络包数量和分片、PPS、吞吐量以及错误数
根据上面的结果发现,PPS为10万,吞吐量为55Mb/s,没有发生错误
那么10万PPS好不好呢?
作为对比,可以计算一下千兆交换机的 PPS
交换机可以达到线速(满负载时,无差错转发),它的PPS就是1000Mbit除以以太网帧的大小
即1000Mbps/((64+20)*8bit)=1.5Mpps(其中20B为以太网帧的头部大小)
即使是千兆交换机的PPS,也可以达到150万 PPS,比测试得到的10万大多了
所以看到这个数值并不用担心,现在多核服务器和万兆网卡已经很普遍了,稍做优化就可以达到数百万的PPS
而且如果用了上节课讲到的DPDK或XDP,还能达到千万数量级
TCP/UDP性能
TCP和UDP的性能测试方法,使用iperf或者netperf工具
特别是现在的云计算时代,刚拿到一批虚拟机时
首先要做的,应该就是用iperf测试一下网络性能是否符合预期
-
iperf和netperf都是最常用的网络性能测试工具,测试TCP和UDP的吞吐量
它们都以客户端和服务器通信的方式,测试一段时间内的平均吞吐量# Ubuntu root@alnk:~# apt-get install iperf3 # CentOS yum install iperf3
-
在目标机器上启动iperf服务端
# -s 表示启动服务端 # -i 表示汇报间隔 # -p 表示监听端口 root@alnk:~# iperf3 -s -i 1 -p 10000
-
在另一台机器上运行iperf客户端运行测试
# -c 表示启动客户端192.168.0.30为目标服务器的IP # -b 表示目标带宽 (单位是bits/s) # -t 表示测试时间 # -P 表示并发数 # -p 表示目标服务器监听端口 [root@local_deploy_192-168-1-5 ~]# iperf3 -c 124.71.83.217 -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 13.0 MBytes 7.28 Mbits/sec receiver [ 7] 0.00-15.03 sec 0.00 Bytes 0.00 bits/sec sender [ 7] 0.00-15.03 sec 12.3 MBytes 6.88 Mbits/sec receiver [SUM] 0.00-15.03 sec 0.00 Bytes 0.00 bits/sec sender [SUM] 0.00-15.03 sec 25.4 MBytes 14.2 Mbits/sec receiver ----------------------------------------------------------- Server listening on 10000 ## 最后的SUM行就是测试的汇总结果,包括测试时间、数据传输量以及带宽等 按照发送和接收,这一部分又分为了sender和receiver两行 从测试结果可以看到,这台机器TCP接收的带宽(吞吐量)为14.2Mb/s 跟目标的1Gb/s相比,还是有些差距的 ## 内网测试 [ ID] Interval Transfer Bandwidth [ 5] 0.00-15.04 sec 0.00 Bytes 0.00 bits/sec sender [ 5] 0.00-15.04 sec 1.71 GBytes 974 Mbits/sec receiver [ 7] 0.00-15.04 sec 0.00 Bytes 0.00 bits/sec sender [ 7] 0.00-15.04 sec 1.71 GBytes 974 Mbits/sec receiver [SUM] 0.00-15.04 sec 0.00 Bytes 0.00 bits/sec sender [SUM] 0.00-15.04 sec 3.41 GBytes 1.95 Gbits/sec receiver
HTTP性能
从传输层再往上,到了应用层
有的应用程序,会直接基于TCP或UDP构建服务
当然也有大量的应用,基于应用层的协议来构建服务,HTTP就是最常用的一个应用层协议
比如常用的Apache、Nginx等各种Web服务,都是基于HTTP
要测试HTTP的性能,也有大量的工具可以使用
比如ab、webbench等,都是常用的 HTTP 压力测试工具
其中,ab是Apache自带的HTTP压测工具
主要测试HTTP服务的每秒请求数、请求延迟、吞吐量以及请求延迟的分布情况等
-
安装ab工具
# Ubuntu $ apt-get install -y apache2-utils # CentOS $ yum install -y httpd-tools
-
在目标机器上使用Docker启动一个Nginx服务,然后用ab来测试它的性能
首先,在目标机器上运行下面的命令root@alnk:~# docker run -p 80:80 -itd nginx
-
在另一台机器上,运行ab命令,测试Nginx的性能
# 公网测试 # -c 表示并发请求数为100 # -n 表示总的请求数为1000 [root@local_deploy_192-168-1-5 ~]# ab -c 100 -n 1000 http://124.71.83.217/ ...... Server Software: nginx/1.21.4 Server Hostname: 124.71.83.217 Server Port: 80 ...... Requests per second: 470.58 [#/sec] (mean) Time per request: 212.506 [ms] (mean) Time per request: 2.125 [ms] (mean, across all concurrent requests) Transfer rate: 391.96 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 6 67 231.6 13 2018 Processing: 6 66 130.4 12 1534 Waiting: 6 38 72.3 12 667 Total: 13 133 281.7 26 2027 Percentage of the requests served within a certain time (ms) 50% 26 66% 28 75% 29 80% 227 90% 240 95% 1015 98% 1226 99% 1233 100% 2027 (longest request) ## ab的测试结果分为三个部分,分别是请求汇总、连接时间汇总还有请求延迟汇总 1. Requests per second为470 2. 每个请求的延迟(Time per request)分为两行 第一行的212ms表示平均延迟,包括了线程运行的调度时间和网络请求响应时间 下一行的2.125ms ,则表示实际请求的响应时间 3. Transfer rate表示吞吐量(BPS)为391KB/s ## 连接时间汇总部分,则是分别展示了建立连接、请求、等待以及汇总等的各类时间 包括最小、最大、平均以及中值处理时间 ## ## 内网测试 root@alnk:~# ab -c 100 -n 1000 http://192.168.0.53/ ...... Server Software: nginx/1.21.4 Server Hostname: 192.168.0.53 Server Port: 80 ...... Requests per second: 12895.41 [#/sec] (mean) Time per request: 7.755 [ms] (mean) Time per request: 0.078 [ms] (mean, across all concurrent requests) Transfer rate: 10729.38 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 1.1 1 4 Processing: 0 6 3.5 6 14 Waiting: 0 6 3.4 5 13 Total: 1 7 3.3 8 14
应用负载性能
当用iperf或者ab等测试工具,得到TCP、HTTP等的性能数据后,这些数据是否就能表示应用程序的实际性能呢?
答案应该是否定的
比如,应用程序基于HTTP协议,为最终用户提供一个Web服务
这时,使用ab工具,可以得到某个页面的访问性能,但这个结果跟用户的实际请求,很可能不一致
因为用户请求往往会附带着各种各种的负载(payload)
而这些负载会影响Web应用程序内部的处理逻辑,从而影响最终性能
那么,为了得到应用程序的实际性能,就要求性能工具本身可以模拟用户的请求负载
而iperf、ab这类工具就无能为力了
可以用wrk、TCPCopy、Jmeter或者LoadRunner等实现这个目标
以wrk为例,它是一个HTTP性能测试工具,内置了LuaJIT
方便根据实际需求,生成所需的请求负载,或者自定义响应的处理方法
wrk工具本身不提供yum或apt的安装方法,需要通过源码编译来安装
比如可以运行下面的命令,来编译和安装 wrk
# 下载zip压缩包,https://github.com/wg/wrk
root@alnk:~# unzip wrk-master.zip
root@alnk:~# cd wrk-master/
root@alnk:~# apt-get install build-essential -y
root@alnk:~# make
root@alnk:~# cp wrk /usr/local/bin/
wrk的命令行参数比较简单
比如可以用wrk ,来重新测一下前面已经启动的Nginx的性能
## 公网测试
# -c 表示并发连接数1000
# -t 表示线程数为2
[root@local_deploy_192-168-1-5 ~]# wrk -c 1000 -t 2 http://124.71.83.217/
Running 10s test @ http://124.71.83.217/
2 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 127.16ms 276.45ms 2.00s 89.95%
Req/Sec 592.16 1.77k 11.69k 95.29%
11283 requests in 10.02s, 9.42MB read
Socket errors: connect 0, read 0, write 209, timeout 592
Requests/sec: 1125.98
Transfer/sec: 0.94MB
##
## 内网测试
root@alnk:~# wrk -c 1000 -t 2 http://192.168.0.53/
Running 10s test @ http://192.168.0.53/
2 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 30.99ms 18.12ms 305.08ms 78.50%
Req/Sec 15.57k 2.97k 21.65k 74.75%
310194 requests in 10.09s, 253.52MB read
Requests/sec: 30729.55
Transfer/sec: 25.12MB
##
这里使用2个线程、并发1000连接,重新测试了Nginx的性能
可以看到,每秒请求数为30729,吞吐量为25.12MB,平均延迟为30.99ms,比前面ab的测试结果要好很多
这也说明性能工具本身的性能,对性能测试也是至关重要的
不合适的性能工具,并不能准确测出应用程序的最佳性能
当然wrk最大的优势,是其内置的LuaJIT,可以用来实现复杂场景的性能测试
wrk在调用Lua脚本时,可以将HTTP请求分为三个阶段,即setup、running、done
如下图所示
比如可以在setup阶段,为请求设置认证参数(来自于wrk官方示例)
root@alnk:~# cat auth.lua
-- 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选项,执行脚本的路径
root@alnk:~# wrk -c 1000 -t 2 -s auth.lua http://192.168.0.53/
Running 10s test @ http://192.168.0.53/
2 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 38.00ms 45.83ms 1.13s 96.61%
Req/Sec 14.11k 3.04k 19.90k 61.31%
280685 requests in 10.06s, 82.45MB read
Non-2xx or 3xx responses: 280685
Requests/sec: 27898.25
Transfer/sec: 8.19MB
##
wrk要用Lua脚本,来构造请求负载
这对于大部分场景来说,可能已经足够了
不过,它的缺点也正是,所有东西都需要代码来构造,并且工具本身不提供GUI环境
##
像Jmeter或者LoadRunner(商业产品),则针对复杂场景提供了脚本录制、回放、GUI等更丰富的功能
使用起来也更加方便
小结
性能评估是优化网络性能的前提,只有在发现网络性能瓶颈时,才需要进行网络性能优化
根据TCP/IP协议栈的原理,不同协议层关注的性能重点不完全一样,也就对应不同的性能测试方法
- 在应用层,可以使用wrk、Jmeter等模拟用户的负载,测试应用程序的每秒请求数、 处理延迟、错误数等
- 在传输层,则可以使用iperf等工具,测试TCP的吞吐情况
- 再向下,还可以用Linux内核自带的pktgen测试服务器的PPS
由于低层协议是高层协议的基础
所以,一般情况下,需要从上到下,对每个协议层进行性能测试
然后根据性能测试的结果,结合Linux网络协议栈的原理,找出导致性能瓶颈的根源,进而优化网络性能