这篇文章上次修改于 954 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
1 现象
# -c 表示发送 10 个请求,-S 表示使用 TCP SYN,-p 指定端口为 80
$ hping3 -c 10 -S -p 80 192.168.0.30
HPING 192.168.0.30 (eth0 192.168.0.30): S set, 40 headers + 0 data bytes
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=3 win=5120 rtt=7.5 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=4 win=5120 rtt=7.4 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=5 win=5120 rtt=3.3 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=7 win=5120 rtt=3.0 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=6 win=5120 rtt=3027.2 ms
--- 192.168.0.30 hping statistic ---
10 packets transmitted, 5 packets received, 50% packet loss
round-trip min/avg/max = 3.0/609.7/3027.2 ms
发送了 10 个请求包,却只收到了 5 个回复,50% 的包都丢了。
2 排查
可能丢包的原因:
- 两台机器连接之间,可能会发生传输失败的错误,比如网络拥塞、线路错误等;
- 网卡收包后,环形缓冲区可能会因为溢出而丢包;
- 链路层,可能会因为网络帧校验失败、QoS 等而丢包;
- IP 层,可能会因为路由失败、组包大小超过 MTU 等而丢包;
- 传输层,可能会因为端口未监听、资源占用超过内核限制等而丢包;
- 套接字层,可能会因为套接字缓冲区溢出而丢包;
- 应用层,可能会因为应用程序异常而丢包;
- 如果配置了 iptables 规则,可能因为 iptables 过滤规则而丢包。
2.1 链路层
当缓冲区溢出等原因导致网卡丢包时,Linux 会在网卡收发数据的统计信息中,记录下收发错误的次数。
查看网卡的丢包记录:
root@nginx:/# netstat -i
Kernel Interface table
Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0 100 31 0 0 0 8 0 0 0 BMRU
lo 65536 0 0 0 0 0 0 0 0 LRU
没有发现任何错误,说明容器的虚拟网卡没有丢包。不过要注意,如果用 tc 等工具配置了 QoS,那么 tc 规则导致的丢包,就不会包含在网卡的统计信息中。
检查一下 eth0 上是否配置了 tc 规则,是否丢包:
root@nginx:/# tc -s qdisc show dev eth0
qdisc netem 800d: root refcnt 2 limit 1000 loss 30%
Sent 432 bytes 8 pkt (dropped 4, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
eth0 上面配置了一个网络模拟排队规则(qdisc netem),并且配置了丢包率为 30%(loss 30%)。再看后面的统计信息,发送了 8 个包,但是丢了 4 个。应该是 netem 模块丢弃了 Nginx 的响应包。
删除 tc 中的 netem 模块:
root@nginx:/# tc qdisc del dev eth0 root netem loss 30%
重新执行 hping3 命令后,发现问题没有解决。
2.2 网络层和传输层
查看协议收发汇总:
root@nginx:/# netstat -s
Ip:
Forwarding: 1 //开启转发
31 total packets received //总收包数
0 forwarded //转发包数
0 incoming packets discarded //接收丢包数
25 incoming packets delivered //接收的数据包数
15 requests sent out //发出的数据包数
Icmp:
0 ICMP messages received //收到的ICMP包数
0 input ICMP message failed //收到ICMP失败数
ICMP input histogram:
0 ICMP messages sent //ICMP发送数
0 ICMP messages failed //ICMP失败数
ICMP output histogram:
Tcp:
0 active connection openings //主动连接数
0 passive connection openings //被动连接数
11 failed connection attempts //失败连接尝试数
0 connection resets received //接收的连接重置数
0 connections established //建立连接数
25 segments received //已接收报文数
21 segments sent out //已发送报文数
4 segments retransmitted //重传报文数
0 bad segments received //错误报文数
0 resets sent //发出的连接重置数
Udp:
0 packets received
...
TcpExt:
11 resets received for embryonic SYN_RECV sockets //半连接重置数
0 packet headers predicted
TCPTimeouts: 7 //超时数
TCPSynRetrans: 4 //SYN重传数
...
只有 TCP 协议发生了丢包和重传,且主要错误是半连接重置。换句话说,主要的失败,都是三次握手失败。但是失败的根源还未找到。
2.3 iptables
iptables 和内核的连接跟踪机制也可能会导致丢包。要确认是不是连接跟踪导致的问题,其实只需要对比当前的连接跟踪数和最大连接跟踪数即可。
连接跟踪在 Linux 内核中是全局的(不属于网络命名空间)。查看连接跟踪数:
$ sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 262144
$ sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 182
连接数小于最大连接数,所以不是连接跟踪导致的。
执行 iptables 命令,查看 filter 表的统计数据:
root@nginx:/# iptables -t filter -nvL
Chain INPUT (policy ACCEPT 25 packets, 1000 bytes)
pkts bytes target prot opt in out source destination
6 240 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.29999999981
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 15 packets, 660 bytes)
pkts bytes target prot opt in out source destination
6 264 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.29999999981
两条 DROP 规则的统计数值不是 0,它们分别在 INPUT 和 OUTPUT 链中。这两条规则实际上是一样的,指的是使用 statistic 模块,进行随机 30% 的丢包。
删除这两条 DROP 规则:
root@nginx:/# iptables -t filter -D INPUT -m statistic --mode random --probability 0.30 -j DROP
root@nginx:/# iptables -t filter -D OUTPUT -m statistic --mode random --probability 0.30 -j DROP
重新执行 hping3,发现丢包问题解决了。
检查 Nginx 对 HTTP 请求的响应:
$ curl --max-time 3 http://192.168.0.30
curl: (28) Operation timed out after 3000 milliseconds with 0 bytes received
发现连接超时了。继续执行 hping3 命令,发现还是正常的,这时候需要抓包了。
2.4 tcpdump
在终端一中执行 tcpdump 命令:
root@nginx:/# tcpdump -i eth0 -nn port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
在终端二再次执行前面的 curl 命令。然后在终端一会看到如下输出:
14:40:00.589235 IP 10.255.255.5.39058 > 172.17.0.2.80: Flags [S], seq 332257715, win 29200, options [mss 1418,sackOK,TS val 486800541 ecr 0,nop,wscale 7], length 0
14:40:00.589277 IP 172.17.0.2.80 > 10.255.255.5.39058: Flags [S.], seq 1630206251, ack 332257716, win 4880, options [mss 256,sackOK,TS val 2509376001 ecr 486800541,nop,wscale 7], length 0
14:40:00.589894 IP 10.255.255.5.39058 > 172.17.0.2.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 486800541 ecr 2509376001], length 0
14:40:03.589352 IP 10.255.255.5.39058 > 172.17.0.2.80: Flags [F.], seq 76, ack 1, win 229, options [nop,nop,TS val 486803541 ecr 2509376001], length 0
14:40:03.589417 IP 172.17.0.2.80 > 10.255.255.5.39058: Flags [.], ack 1, win 40, options [nop,nop,TS val 2509379001 ecr 486800541,nop,nop,sack 1 {76:77}], length 0
前三个包是正常的三次握手,第四个包确在 3s 后才到,还是客户端发来的 FIN 包,说明是客户端的连接关闭了。我们并没有抓到 curl 发来的 HTTP Get 请求,究竟是网卡丢包了,还是客户端根本没有发来呢?
查看网卡有没有丢包:
root@nginx:/# netstat -i
Kernel Interface table
Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0 100 157 0 344 0 94 0 0 0 BMRU
lo 65536 0 0 0 0 0 0 0 0 LRU
接收包丢包数是 344,看来是在网卡接收时丢包了。但为什么 hping3 时不丢包,但是 GET 会丢包呢?对比下 hping3 和 curl 两个工具:
- hping3 只发送了 SYN 包。
- curl 在发送了 SYN 包后,还会发送 HTTP GET 请求包。HTTP GET 请求本质上也是 TCP 包,但是还携带了 HTTP GET 数据。
可能是 MTU 配置错误导致的。继续查看网卡的输出,eth0 的 MTU 只有 100,太小了,修改成 1500:
root@nginx:/# ifconfig eth0 mtu 1500
再次执行 curl 命令,发现收到了正常的响应。
参考
倪鹏飞. Linux 性能优化实战.
没有评论