TCP 三次握手原理,你真的理解吗?
关于TCP 半连接队列和全连接队列
网络性能指标(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 | 套路篇:怎么评估系统的网络性能?