Linux性能优化实战学习笔记:第四十八讲
一、上节回顾
上一节,我们一起学习了如何分析网络丢包的问题,特别是从链路层、网络层以及传输层等主要的协议栈中进行分析。
不过,通过前面这几层的分析,我们还是没有找出最终的性能瓶颈。看来,还是要继续深挖才可以。今天,我们就来继续分析这个未果的案例。
在开始下面的内容前,你可以先回忆一下上节课的内容,并且自己动脑想一想,除了我们提到的链路层、网络层以及传输层之外,还有哪些潜在问题可能会导致丢包呢?
二、iptables
首先我们要知道,除了网络层和传输层的各种协议,iptables 和内核的连接跟踪机制也可能会导致丢包。所以,这也是发生丢包问题时,我们必须要排查的一个因素。
我们先来看看连接跟踪,我已经在 如何优化 NAT 性能 文章中,给你讲过连接跟踪的优化思路。要确认是不是连接跟踪导致的问题,其实只需要对比当前的连接跟踪数和最大连接跟踪数即可。
不过,由于连接跟踪在 Linux 内核中是全局的(不属于网络命名空间),我们需要退出容器终端,回到主机中来查看。
你可以在容器终端中,执行 exit ;然后执行下面的命令,查看连接跟踪数:
# 容器终端中执行 exit
root@nginx:/# exit
exit # 主机终端中查询内核配置
$ sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 262144
$ sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 182
实际测试代码如下:
[root@luoahong ~]# sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 262144
[root@luoahong ~]# sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 11
从这儿你可以看到,连接跟踪数只有 182,而最大连接跟踪数则是 262144。显然,这里的丢包,不可能是连接跟踪导致的。
接着,再来看 iptables。回顾一下 iptables 的原理,它基于 Netfilter 框架,通过一系列的规则,对网络数据包进行过滤(如防火墙)和修改(如 NAT)。
这些 iptables 规则,统一管理在一系列的表中,包括 filter(用于过滤)、nat(用于 NAT)、mangle(用于修改分组数据) 和 raw(用于原始数据包)等。而每张表又可以包括一系列的
链,用于对 iptables 规则进行分组管理。
对于丢包问题来说,最大的可能就是被 filter 表中的规则给丢弃了。要弄清楚这一点,就需要我们确认,那些目标为 DROP 和 REJECT 等会弃包的规则,有没有被执行到。
你可以把所有的 iptables 规则列出来,根据收发包的特点,跟 iptables 规则进行匹配。不过显然,如果 iptables 规则比较多,这样做的效率就会很低。
当然,更简单的方法,就是直接查询 DROP 和 REJECT 等规则的统计信息,看看是否为 0。如果统计值不是 0 ,再把相关的规则拎出来进行分析。
我们可以通过 iptables -nvL 命令,查看各条规则的统计信息。比如,你可以执行下面的 dockerexec 命令,进入容器终端;然后再执行下面的 iptables 命令,就可以看到 filter 表的统计数据了:
# 在主机中执行
$ docker exec -it nginx bash # 在容器中执行
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
实际测试代码如下:
root@nginx:/# iptables -t filter -nvL
Chain INPUT (policy ACCEPT 24 packets, 960 bytes)
pkts bytes target prot opt in out source destination
7 280 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 23 packets, 1012 bytes)
pkts bytes target prot opt in out source destination
8 352 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.29999999981
root@nginx:/#
从 iptables 的输出中,你可以看到,两条 DROP 规则的统计数值不是 0,它们分别在 INPUT 和OUTPUT 链中。这两条规则实际上是一样的,指的是使用 statistic 模块,进行随机 30% 的丢包。
再观察一下它们的匹配规则。0.0.0.0/0 表示匹配所有的源 IP 和目的 IP,也就是会对所有包都进行随机 30% 的丢包。看起来,这应该就是导致部分丢包的“罪魁祸首”了。
既然找出了原因,接下来的优化就比较简单了。比如,把这两条规则直接删除就可以了。我们可以在容器终端中,执行下面的两条 iptables 命令,删除这两条 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 命令,看看现在是否正常:
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=0 win=5120 rtt=11.9 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=1 win=5120 rtt=7.8 ms
...
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=9 win=5120 rtt=15.0 ms --- 192.168.0.30 hping statistic ---
10 packets transmitted, 10 packets received, 0% packet loss
round-trip min/avg/max = 3.3/7.9/15.0 ms
实际测试代码如下:
root@luoahong:~# hping3 -c 10 -S -p 80 192.168.118.85
HPING 192.168.118.85 (ens33 192.168.118.85): S set, 40 headers + 0 data bytes
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=1 win=65535 rtt=3.9 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=65535 rtt=1013.3 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=4 win=65535 rtt=8.7 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=3 win=65535 rtt=1017.5 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=5 win=65535 rtt=7.3 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=7 win=65535 rtt=5.0 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=8 win=65535 rtt=10.8 ms --- 192.168.118.85 hping statistic ---
10 packets transmitted, 7 packets received, 30% packet loss
round-trip min/avg/max = 3.9/295.2/1017.5 ms
这次输出你可以看到,现在已经没有丢包了,并且延迟的波动变化也很小。看来,丢包问题应该已经解决了。
不过,到目前为止,我们一直使用的 hping3 工具,只能验证案例 Nginx 的 80 端口处于正常监听状态,却还没有访问 Nginx 的 HTTP 服务。所以,不要匆忙下结论结束这次优化,我们还需
要进一步确认,Nginx 能不能正常响应 HTTP 请求。
我们继续在终端二中,执行如下的 curl 命令,检查 Nginx 对 HTTP 请求的响应:
curl --max-time 3 http://192.168.0.30
curl: (28) Operation timed out after 3000 milliseconds with 0 bytes received
实际测试代码如下:
root@luoahong:~# curl --max-time 3 http://192.168.118.85
curl: (28) Operation timed out after 3002 milliseconds with 0 bytes received
从 curl 的输出中,你可以发现,这次连接超时了。可是,刚才我们明明用 hping3 验证了端口正常,现在却发现 HTTP 连接超时,是不是因为 Nginx 突然异常退出了呢?
不妨再次运行 hping3 来确认一下:
hping3 -c 3 -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=0 win=5120 rtt=7.8 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=1 win=5120 rtt=7.7 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=2 win=5120 rtt=3.6 ms --- 192.168.0.30 hping statistic ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 3.6/6.4/7.8 ms
实际测试代码如下:
root@luoahong:~# hping3 -c 10 -S -p 80 192.168.118.85
HPING 192.168.118.85 (ens33 192.168.118.85): S set, 40 headers + 0 data bytes
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=1 win=65535 rtt=3.9 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=65535 rtt=1013.3 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=4 win=65535 rtt=8.7 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=3 win=65535 rtt=1017.5 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=5 win=65535 rtt=7.3 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=7 win=65535 rtt=5.0 ms
len=46 ip=192.168.118.85 ttl=63 DF id=0 sport=80 flags=SA seq=8 win=65535 rtt=10.8 ms --- 192.168.118.85 hping statistic ---
10 packets transmitted, 7 packets received, 30% packet loss
round-trip min/avg/max = 3.9/295.2/1017.5 ms
奇怪,hping3 的结果显示,Nginx 的 80 端口确确实实还是正常状态。这该如何是好呢?别忘了,我们还有个大杀器——抓包操作。看来有必要抓包看看了。
三、tcpdump
接下来,我们切换回终端一,在容器终端中,执行下面的 tcpdump 命令,抓取 80 端口的包:
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 命令:
curl --max-time 3 http://192.168.0.30/
curl: (28) Operation timed out after 3000 milliseconds with 0 bytes received
实际测试代码如下:
root@luoahong:~# curl --max-time 3 http://192.168.118.85
curl: (28) Operation timed out after 3002 milliseconds with 0 bytes received
等到 curl 命令结束后,再次切换回终端一,查看 tcpdump 的输出:
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
实际测试代码如下:
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
08:48:31.746239 IP 192.168.118.77.47274 > 172.17.0.2.80: Flags [S], seq 3419504692, win 29200, options [mss 1460,sackOK,TS val 109552116 ecr 0,nop,wscale 7], length 0
08:48:31.746400 IP 172.17.0.2.80 > 192.168.118.77.47274: Flags [S.], seq 1891132195, ack 3419504693, win 65392, options [mss 256,sackOK,TS val 2205929180 ecr 109552116,nop,wscale 7], length 0
08:48:31.747134 IP 192.168.118.77.47274 > 172.17.0.2.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 109552117 ecr 2205929180], length 0
08:48:34.748411 IP 192.168.118.77.47274 > 172.17.0.2.80: Flags [F.], seq 79, ack 1, win 229, options [nop,nop,TS val 109555119 ecr 2205929180], length 0
08:48:34.748567 IP 172.17.0.2.80 > 192.168.118.77.47274: Flags [.], ack 1, win 511, options [nop,nop,TS val 2205932182 ecr 109552117,nop,nop,sack 1 {79:80}], length 0
^C
5 packets captured
5 packets received by filter
0 packets dropped by kernel
22 packets dropped by interface
经过这么一系列的操作,从 tcpdump 的输出中,我们就可以看到:
- 前三个包是正常的 TCP 三次握手,这没问题;
- 但第四个包却是在 3 秒以后了,并且还是客户端(VM2)发送过来的 FIN 包,也就说明,客户端的连接关闭了。
我想,根据 curl 设置的 3 秒超时选项,你应该能猜到,这是因为 curl 命令超时后退出了。我把这一过程,用 TCP 交互的流程图(实际上来自 Wireshark 的 Flow Graph)来表示,你可
以更清楚地看到上面这个问题:

这里比较奇怪的是,我们并没有抓取到 curl 发来的 HTTP GET 请求。那么,究竟是网卡丢包了,还是客户端压根儿就没发过来呢?
我们可以重新执行 netstat -i 命令,确认一下网卡有没有丢包问题:
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
从 netstat 的输出中,你可以看到,接收丢包数(RX-DRP)是 344,果然是在网卡接收时丢包了。不过问题也来了,为什么刚才用 hping3 时不丢包,现在换成 GET 就收不到了呢?
还是那句话,遇到搞不懂的现象,不妨先去查查工具和方法的原理。我们可以对比一下这两个工具:
- hping3 实际上只发送了 SYN 包;
- 而 curl 在发送 SYN 包后,还会发送 HTTP GET 请求。
HTTP GET ,本质上也是一个 TCP 包,但跟 SYN 包相比,它还携带了 HTTP GET 的数据。那么,通过这个对比,你应该想到了,这可能是 MTU 配置错误导致的。为什么呢?
其实,仔细观察上面 netstat 的输出界面,第二列正是每个网卡的 MTU 值。eth0 的 MTU 只有100,而以太网的 MTU 默认值是 1500,这个 100 就显得太小了。
当然,MTU 问题是很好解决的,把它改成 1500 就可以了。我们继续在容器终端中,执行下面的命令,把容器 eth0 的 MTU 改成 1500:
root@nginx:/# ifconfig eth0 mtu 1500
修改完成后,再切换到终端二中,再次执行 curl 命令,确认问题是否真的解决了:
curl --max-time 3 http://192.168.0.30/
<!DOCTYPE html>
<html>
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
实际测试代码如下:
root@luoahong:~# curl --max-time 3 http://192.168.118.85
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p> <p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p>
</body>
</html>
非常不容易呀,这次终于看到了熟悉的 Nginx 响应,说明丢包的问题终于彻底解决了。
当然,案例结束前,不要忘记停止今天的 Nginx 应用。你可以切换回终端一,在容器终端中执行exit 命令,退出容器终端:
root@nginx:/# exit
exit
最后,再执行下面的 docker 命令,停止并删除 Nginx 容器:
docker rm -f nginx
四、小结
今天,我继续带你分析了网络丢包的问题。特别是在时不时丢包的情况下,定位和优化都需要我们花心思重点投入。
网络丢包问题的严重性不言而喻。碰到丢包问题时,我们还是要从 Linux 网络收发的流程入手,结合 TCP/IP 协议栈的原理来逐层分析。
Linux性能优化实战学习笔记:第四十八讲的更多相关文章
- Linux性能优化实战学习笔记:第十八讲
一.内存的分配和回收 1.管理内存的过程中,也很容易发生各种各样的“事故”, 对应用程序来说,动态内存的分配和回收,是既核心又复杂的一的一个逻辑功能模块.管理内存的过程中,也很容易发生各种各样的“事故 ...
- Linux性能优化实战学习笔记:第二十八讲
一.案例环境描述 1.环境准备 2CPU,4GB内存 预先安装docker sysstat工具 apt install docker.io sysstat nake git 案例总共由三个容器组成: ...
- Linux性能优化实战学习笔记:第十二讲
一.性能优化方法论 不可中断进程案例 二.怎么评估性能优化的效果? 1.评估思路 2.几个为什么 1.为什么要选择不同维度的指标? 应用程序和系统资源是相辅相成的关系 2.性能优化的最终目的和结果? ...
- Linux性能优化实战学习笔记:第十六讲
一.free数据的来源 1.碰到看不明白的指标时该怎么办吗? 不懂就去查手册.用 man 命令查询 free 的文档.就可以找到对应指标的详细说明.比如,我们执行 man fre... 2.free数 ...
- Linux性能优化实战学习笔记:第二十六讲
一.案例环境描述 1.环境准备 2CPU,4GB内存 预先安装docker sysstat工具 2.温馨提示 案例中 Python 应用的核心逻辑比较简单,你可能一眼就能看出问题,但实际生产环境中的源 ...
- Linux性能优化实战学习笔记:第三十一讲
一.上节回顾 上一节,我们一起回顾了常见的文件系统和磁盘 I/O 性能指标,梳理了核心的 I/O 性能观测工具,最后还总结了快速分析 I/O 性能问题的思路. 虽然 I/O 的性能指标很多,相应的性能 ...
- Linux性能优化实战学习笔记:第五十七讲
一.上节回顾 上一节,我带你一起梳理了常见的性能优化思路,先简单回顾一下.我们可以从系统和应用程序两个角度,来进行性能优化. 从系统的角度来说,主要是对 CPU.内存.网络.磁盘 I/O 以及内核软件 ...
- Linux性能优化实战学习笔记:第二十四讲
一.磁盘 1.机械磁盘 2.固态磁盘 3.相同磁盘随机I/O比连续I/O慢很多 4.最小单位 5.接口 6.RAID陈列卡 7.网路存储 二.通用块层 1.概念 2.第一功能 3.第二功能 4.I/O ...
- Linux性能优化实战学习笔记:第三十七讲
一.上节回顾 上一节,我带你一起学习了网络性能的评估方法.简单回顾一下,Linux 网络基于 TCP/IP协议栈构建,而在协议栈的不同层,我们所关注的网络性能也不尽相同. 在应用层,我们关注的是应用程 ...
- Linux性能优化实战学习笔记:第五十一讲
一.上节回顾 上一节,我带你一起学习了常见的动态追踪方法.所谓动态追踪,就是在系统或者应用程序正常运行的时候,通过内核中提供的探针,来动态追踪它们的行为,从而辅助排查出性能问题的瓶颈. 使用动态追踪, ...
随机推荐
- JeeSite | 数据权限应用
中午吃饭时看了一下陆毅版的<三国>,刚好看的是蜀军缺粮,诸葛亮让王平去劫司马懿的粮.司马懿看蜀军用木牛流马运量很方便,就抢了蜀军的木牛流马仿制了一批,结果司马懿用它运粮时,被王平冒充司马懿 ...
- git 给分支添加描述 管理分支实用方法
1.背景 在我们工作中,正常情况我们处在一个迭代中,一个人最多会有几个功能,比较正常的操作我们会给每个大功能创建不同的分支,方便管理. 我们可以非常愉快的进行版本管理,遇到特殊情况我们也可以方便版本退 ...
- tf.slice()
原文连接:https://www.jianshu.com/p/71e6ef6c121b tf.slice()到底要怎么切呢?下面通过列子来看看 方程的signature是这样的: def slice( ...
- Vue 结合 SignalR 实现前后端实时消息同步
最近业务中需要实现服务器端与客户端的实时通信功能,对Signalr做了一点总结和整理. SignalR 作为 ASP.NET 的一个库,能够简单方便地为应用提供实时的服务器端与客户端双向通信功能. ...
- Kubernetes Pod 镜像拉取策略
Kubernetes Pod 镜像拉取策略 官方文档:https://kubernetes.io/docs/concepts/containers/images/ • IfNotPresent:默认值 ...
- 时间复杂度o(1), o(n), o(logn), o(nlogn)
1.时间复杂度o(1), o(n), o(logn), o(nlogn).算法时间复杂度的时候有说o(1), o(n), o(logn), o(nlogn),这是算法的时空复杂度的表示.不仅仅用于表示 ...
- Java生鲜电商平台-定时器,定时任务quartz的设计与架构
Java生鲜电商平台-定时器,定时任务quartz的设计与架构 说明:任何业务有时候需要系统在某个定点的时刻执行某些任务,比如:凌晨2点统计昨天的报表,早上6点抽取用户下单的佣金. 对于Java开源生 ...
- 最新整理的spring面试题从基础到高级,干货满满
最新整理的spring面试题从基础到高级,干货满满 前言: 收藏了一些关于Spring的面试题,一方面是为了准备找工作的时候看面试题,另一方面,通过面试题的方式加深一些自己的理论知识. spring ...
- PHP格林威治时间
echo gmdate('D, d M Y H:i:s \G\M\T'); echo '<br>'; echo gmdate ("l, d F Y H:i:s").&q ...
- 学习shiro第二天
昨天讲了shiro的认证流程以及代码实现,今天将对这个进行扩展. 因为我们的测试数据是shiro.ini文件中配置的静态数据,但实际上数据应该从数据库中查询出来才合理,因此我们今天讲讲JdbcReal ...