36-套路篇:怎么评估系统的网络性能?





网络性能指标

实际上通常用带宽、吞吐量、延时、PPS(Packet Per Second)等指标衡量网络的性能

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

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




网络基准测试

Linux网络基于TCP/IP协议栈,而不同协议层的行为显然不同
那么测试之前应该弄清楚要评估的网络性能,究竟属于协议栈的哪一层?
换句话说应用程序基于协议栈的哪一层呢?

根据前面学过的 TCP/IP 协议栈的原理

  1. 基于HTTP或者HTTPS的Web应用程序,显然属于应用层,需要测试HTTP/HTTPS的性能
  2. 对大多数游戏服务器来说,为了支持更大的同时在线人数
    通常会基于TCP或UDP 与客户端进行交互,这时就需要我们测试TCP/UDP的性能
  3. 把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测试一下网络性能是否符合预期

  1. iperf和netperf都是最常用的网络性能测试工具,测试TCP和UDP的吞吐量
    它们都以客户端和服务器通信的方式,测试一段时间内的平均吞吐量

    # Ubuntu
    root@alnk:~# apt-get install iperf3
    
    # CentOS
    yum install iperf3
    
    
  2. 在目标机器上启动iperf服务端

    # -s 表示启动服务端
    # -i 表示汇报间隔
    # -p 表示监听端口
    root@alnk:~# iperf3 -s -i 1 -p 10000
    
    
  3. 在另一台机器上运行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
    
    
  4. 稍等一会儿测试结束后,回到目标服务器查看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服务的每秒请求数、请求延迟、吞吐量以及请求延迟的分布情况等

  1. 安装ab工具

    # Ubuntu
    $ apt-get install -y apache2-utils
    # CentOS
    $ yum install -y httpd-tools
    
    
  2. 在目标机器上使用Docker启动一个Nginx服务,然后用ab来测试它的性能
    首先,在目标机器上运行下面的命令

    root@alnk:~# docker run -p 80:80 -itd nginx
    
    
  3. 在另一台机器上,运行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
如下图所示
image-20211228151356969

比如可以在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协议栈的原理,不同协议层关注的性能重点不完全一样,也就对应不同的性能测试方法

  1. 在应用层,可以使用wrk、Jmeter等模拟用户的负载,测试应用程序的每秒请求数、 处理延迟、错误数等
  2. 在传输层,则可以使用iperf等工具,测试TCP的吞吐情况
  3. 再向下,还可以用Linux内核自带的pktgen测试服务器的PPS

由于低层协议是高层协议的基础
所以,一般情况下,需要从上到下,对每个协议层进行性能测试
然后根据性能测试的结果,结合Linux网络协议栈的原理,找出导致性能瓶颈的根源,进而优化网络性能


posted @ 2021-12-28 15:25  李成果  阅读(729)  评论(0编辑  收藏  举报