这篇文章上次修改于 899 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

1 确定优化目标

具体到不同的应用中,每个指标的优化标准可能不同。

  • 对于 NAT 网关,直接影响到整个数据中心的网络出入性能,需要达到或接近线性转发,PPS 是主要的性能指标。
  • 对于数据库、缓存等,需要快速完成网络收发,低延迟是主要的性能指标。
  • 对于 Web 服务,需同时兼顾吞吐量和延迟。

可按照协议栈的每一层进行基准测试,底层性能决定高层性能。

  • 网络接口层和网络层,每秒可处理的网络包数 PPS 是重要的性能指标,可用内核自带的工具 pktgen 测试。
  • 传输层,吞吐量(BPS)、连接数和延迟是重要的性能指标,可用 perf 或 iperf 测试,不过要注意测试不同网络包下的性能。
  • 应用层,关注的是吞吐量(BPS)、延迟和每秒请求数等指标,可用 wrk、ab 等测试。

2 网络性能工具

2.1 根据指标查找工具

2.1.1 吞吐量(BPS)

sar 可查看网络接口的吞吐量。rxkB/s 和 txkB/s 分别是接收和发送的吞吐量,单位是 KB/ 秒。

# 数字1表示每隔1秒输出一组数据
$ sar -n DEV 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

2.1.2 PPS

sar 可查看网络接口的 PPS。rxpck/s 和 txpck/s 分别是接收和发送的 PPS,单位为包 / 秒。

2.1.3 连接数

# 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))

当套接字处于 Established 时:

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

当套接字处于 Listening 时:

  • Recv-Q 表示全连接队列的长度。
  • 而 Send-Q 表示全连接队列的最大长度。

2.1.4 延迟

ping 通过 ICMP 测试网络延迟。

# -c3表示发送三次ICMP包后停止
$ ping -c3 114.114.114.114
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

hping3 通过 TCP 测试网络延迟。

# -c表示发送3次请求,-S表示设置TCP SYN,-p表示端口号为80
$ hping3 -c 3 -S -p 80 baidu.com
HPING baidu.com (eth0 123.125.115.110): S set, 40 headers + 0 data bytes
len=46 ip=123.125.115.110 ttl=51 id=47908 sport=80 flags=SA seq=0 win=8192 rtt=20.9 ms
len=46 ip=123.125.115.110 ttl=51 id=6788  sport=80 flags=SA seq=1 win=8192 rtt=20.9 ms
len=46 ip=123.125.115.110 ttl=51 id=37699 sport=80 flags=SA seq=2 win=8192 rtt=20.9 ms

--- baidu.com hping statistic ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 20.9/20.9/20.9 ms

2.1.5 连接跟踪数

$ sysctl -a | grep conntrack
net.netfilter.nf_conntrack_count = 180
net.netfilter.nf_conntrack_max = 1000
net.netfilter.nf_conntrack_buckets = 65536
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 60
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 120
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
...
  • net.netfilter.nf_conntrack_count,表示当前连接跟踪数;2
  • net.netfilter.nf_conntrack_max,表示最大连接跟踪数;
  • net.netfilter.nf_conntrack_buckets,表示连接跟踪表的大小。

2.1.6 路由

traceroute 会在路由的每一跳发送三个包,并在收到响应后,输出往返延时。如果无响应或者响应超时(默认 5s),就会输出一个星号。

# --tcp表示使用TCP协议,-p表示端口号,-n表示不对结果中的IP地址执行反向域名解析
$ traceroute --tcp -p 80 -n baidu.com
traceroute to baidu.com (123.125.115.110), 30 hops max, 60 byte packets
 1  * * *
 2  * * *
 3  * * *
 4  * * *
 5  * * *
 6  * * *
 7  * * *
 8  * * *
 9  * * *
10  * * *
11  * * *
12  * * *
13  * * *
14  123.125.115.110  20.684 ms *  20.798 ms

2.1.7 DNS

排查 DNS 解析问题。

$ nslookup time.geekbang.org
# 域名服务器及端口信息
Server:    114.114.114.114
Address:  114.114.114.114#53

# 非权威查询结果
Non-authoritative answer:
Name:  time.geekbang.org
Address: 39.106.233.17

dig 可展示递归查询的整个过程。

# +trace表示开启跟踪查询
# +nodnssec表示禁止DNS安全扩展
$ dig +trace +nodnssec time.geekbang.org

; <<>> DiG 9.11.3-1ubuntu1.3-Ubuntu <<>> +trace +nodnssec time.geekbang.org
;; global options: +cmd
.      322086  IN  NS  m.root-servers.net.
.      322086  IN  NS  a.root-servers.net.
.      322086  IN  NS  i.root-servers.net.
.      322086  IN  NS  d.root-servers.net.
.      322086  IN  NS  g.root-servers.net.
.      322086  IN  NS  l.root-servers.net.
.      322086  IN  NS  c.root-servers.net.
.      322086  IN  NS  b.root-servers.net.
.      322086  IN  NS  h.root-servers.net.
.      322086  IN  NS  e.root-servers.net.
.      322086  IN  NS  k.root-servers.net.
.      322086  IN  NS  j.root-servers.net.
.      322086  IN  NS  f.root-servers.net.
;; Received 239 bytes from 114.114.114.114#53(114.114.114.114) in 1340 ms

org.      172800  IN  NS  a0.org.afilias-nst.info.
org.      172800  IN  NS  a2.org.afilias-nst.info.
org.      172800  IN  NS  b0.org.afilias-nst.org.
org.      172800  IN  NS  b2.org.afilias-nst.org.
org.      172800  IN  NS  c0.org.afilias-nst.info.
org.      172800  IN  NS  d0.org.afilias-nst.org.
;; Received 448 bytes from 198.97.190.53#53(h.root-servers.net) in 708 ms

geekbang.org.    86400  IN  NS  dns9.hichina.com.
geekbang.org.    86400  IN  NS  dns10.hichina.com.
;; Received 96 bytes from 199.19.54.1#53(b0.org.afilias-nst.org) in 1833 ms

time.geekbang.org.  600  IN  A  39.106.233.176
;; Received 62 bytes from 140.205.41.16#53(dns10.hichina.com) in 4 ms

2.1.8 防火墙和 NAT

配置 SNAT:

$ iptables -t nat -A POSTROUTING -s 192.168.0.0/16 -j MASQUERADE
$ iptables -t nat -A POSTROUTING -s 192.168.0.2 -j SNAT --to-source 100.100.100.100

配置 DNAT:

$ iptables -t nat -A PREROUTING -d 100.100.100.100 -j DNAT --to-destination 192.168.0.2

查看 NAT:

$ iptables -nL -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

...

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:172.17.0.2:8080

2.1.9 抓包

$ tcpdump -nn udp port 53 or host 35.190.27.188 -w ping.pcap

-nn 表示不解析 IP 地址和端口号名称。

然后用 Wireshark 打开 ping.pcap,能以更规整的格式看到抓包内容。

2.1.10 内核协议栈跟踪

$ stap --all-modules dropwatch.stp
Monitoring for dropped packets

其中,dropwatch.stp 为用户自己写的脚本。

2.2 根据工具查指标

  • ifconfig、ip:配置和查看网络接口。
  • ss:查看网络连接数。
  • sar:查看网络接口和网络收发情况。
  • nethogs:查看进程的网络收发情况。
  • iftop:查看 IP 的网络收发情况。
  • ethool:查看和配置网络接口。
  • conntrack:查看和管理连接跟踪情况。
  • nslookup、dig:排查 DNS 解析问题。
  • traceroute:查看路由并测试链路信息。
  • ping、hping3:测试网络延迟。
  • tcpdump:网络抓包工具。
  • Wireshark:网络抓包和图形界面分析工具。
  • iptables:配置管理防火墙及 NAT 规则。
  • perf:剖析内核协议栈性能。
  • systemtap:动态追踪内核协议栈行为。

3 网络性能优化

3.1 应用程序

从网络 I/O 角度:

  • I/O 多路复用技术 epoll。
  • 异步 I/O。

从进程的工作模型:

  • 主进程 + 多个 work 子进程。
  • 监听相同端口的多进程模型。

应用层的网络协议优化:

  • 长连接取代短连接。
  • 使用内存等方式缓存不常变化的数据。
  • 使用 Protocol Buffer 序列化等方式压缩网络 I/O 数据量。
  • 使用 DNS 缓存、预取、HTTPDNS 等方式减少 DNS 解析延迟。

3.2 套接字

调整缓冲区大小,比如:

  • 增大每个套接字的缓冲区大小 net.core.optmem_tax
  • 增大套接字接收缓冲区大小 net.core.rmem_max 和发送缓冲区大小 net.core.wmem_max
  • 增大 TCP 接收缓冲区大小 net.ipv4.tcp_rmem 和发送缓冲区大小 net.ipv4.tcp_wmem

比如发送缓冲区大小,理想值是吞吐量 * 延迟。

套接字接口也提供了一些配置项,用来改变网络连接行为:

  • 为 TCP 连接设置 TCP_NODELAY 后,可禁用 Nagle 算法。
  • 为 TCP 连接开启 TCP_CORK 后,可以让小包聚合成大包后再发送。
  • 使用 SO_SNDBUF 和 SO_RCVBUF,可分别调整套接字的发送和接收缓冲区大小。

3.3 传输层

请求量比较大时,可能会有大量连接处于 TIME_WAIT 状态,可采用如下措施:

  • 增大处于 TIME_WAIT 状态的连接数量 net.ipv4.tcp_max_tw_buckets,并增大连接跟踪表大小 net.netfilter.nf_conntrack_max
  • 减小 net.ipv4.tcp_fin_timeout 和 net.netfilter.nf_conntrack_tcp_timeout_time_wait,让系统尽快释放它们所占用的资源。
  • 开启端口复用 net.ipv4.tcp_tw_reuse,这样,被 TIME_WAIT 连接占用的端口还可以用到新的连接中。
  • 增大本地端口范围 net.ipv4.ip_local_port_range,可支持更多的连接,提供并发能力。
  • 增大最大文件描述符数量。可用 fs.nr_open 和 fs.file_max 分别增大进程和系统的最大文件描述符数量;或在应用程序的 systemd 配置文件中配置 LimitNOFILE,设置应用程序的最大文件描述符数量。

为缓解 SYN Flood,可:

  • 增大 TCP 半连接的最大数量 net.ipv4.tcp_max_syn_backlog,或者开启 TCP SYN Cookie net.ipv4.tcp_syncookies (这两个选项不可同时使用)。
  • 减少 SYN_RECV 状态的连接重传 SYN + ACK 包的次数 net.ipv4.tcp_synack_tries

在长连接场景中,系统默认的 Keepalive 探测间隔和重试次数一般无法满足应用程序的性能要求,需要进行优化:

  • 缩短最后一次数据包到 Keepalive 探测包的间隔时间 net.ipv4.tcp_keepalive_time
  • 缩短发送 Keepalive 探测包的间隔时间 net.ipv4.tcp_keepalive_intvl
  • 减少 Keepalive 探测包失败后,一直到通知应用程序前的重试次数 net.ipv4.keepalive_probes

3.4 网络层

从路由和转发角度,可调整如下内核选项:

  • 在需要转发的服务器中,比如 NAT 网关或使用 Docker 时,开启 IP 转发,设置 net.ipv4.ip_forward = 1
  • 调整数据包的生存周期 TTL,比如设置 net.ipv4.ip_default_ttl = 64。注意,增大该值会降低系统性能。
  • 开启数据包的反向地址校验,比如设置 net.ipv4.conf.eth0.rp_filter = 1。可防止 IP 欺骗,并减少伪造 IP 带来的 DDos。

从分片的角度,主要是调整 MTU 大小。比如在使用 VXLAN、GRE 等叠加网络技术时,需调大 MTU。

从 ICMP 出发,为避免 ICMP 主机探测、ICMP Flood 等问题,可限制 ICMP 行为:

  • 禁止 ICMP 协议,设置 net.ipv4.icmp_echo_ignore_all = 1
  • 禁止广播 ICMP,设置 net.ipv4.icmp_echo_ignore_broadcasts = 1

3.5 链路层

网卡收到包后调用中断处理程序需要消耗大量 CPU,可将这些中断调度到不同的 CPU 上:

  • 为网卡硬中断配置 CPU 亲和性(smp_affinity),或者开启 irqbalance 服务。
  • 开启 RPS(Receive Packet Steering)和 RFS(Receive Flow Steering),将应用程序和软中断的处理调度到相同 CPU 上,这样就可以增加 CPU 缓存命中率,减少网络延迟。

将内核中通过软中断处理的功能卸载到网卡中,通过硬件来执行;

  • TSO(TCP Segmentation Offload)和 UFO(UDP Fragmentation Offload):在 TCP/UDP 协议中直接发送大包;而 TCP 包的分段(按照 MSS 分段)和 UDP 的分片(按照 MTU 分片)功能由网卡完成。
  • GSO(Generic Segmentation Offload):在网卡不支持 TSO/UFO 时,将 TCP/UDP 包的分段延迟到进入网卡前再执行。不仅可以减少 CPU 的消耗,还可在发生丢包时只重传分段后的包。
  • LRO(Large Receive Offload):在接收 TCP 分段包时,由网卡将其组装合并后,再交给上层网络处理。但在需要 IP 转发的情况下,不能开启 LRO,因为多个包的头部信息不一致,LRO 合并会导致网络包的校验错误。
  • GRO(Genetric Receive Offload):GRO 修复了 LRO 的缺陷,并且更为通用,同时支持 TCP 和 UDP。
  • RSS(Receive Side Scaling):多队列接收,基于多个接收队列来分配网络接收进程,这样可以让多个 CPU 来处理收到的网络包。
  • VXLAN 卸载:让网卡来完成 VXLAN 的组包功能。

优化网络的吞吐量:

  • 开启网络接口的多队列功能。
  • 增大网络接口的缓冲区大小,以及队列长度等,提高网络吞吐量(可能导致延迟增大)。
  • 使用 Traffic Control 工具,为不同网络流量配置 QoS。

参考

倪鹏飞. Linux 性能优化实战.