一个诡异的 200ms 延迟问题排查过程

本文详细描述了生产环境中Nginx遇到的200ms延迟问题,通过排查系统资源、日志分析、抓包和理解TCP机制,最终发现是服务端宿主机特性导致的包序乱序。作者分享了排查过程和解决方案,强调了实践经验的重要性。
摘要由CSDN通过智能技术生成

一个诡异的 200ms 延迟问题排查过程

最近生产环境 Nginx 遇到了部分请求延迟增加200ms的情况,深入排查解决后觉得挺有意义的(包括排查过程),所以这里记录分享一下。

0x01 现象

生产环境有 Nginx 网关,网关上游(upstream)是业务应用。统计发现 Nginx 的 P99 延迟比上游应用统计的 P99 延迟要多大约 100 多毫秒(不同接口时间可能不同)。

图1、网络拓扑

图2、Nginx P99 延迟

图3、应用 P99 延迟

0x02 200ms 的来源

Nginx 中是通过内置 $request_time 变量来获取的单个请求的延迟,在生产环境开启日志记录,发现部分请求延迟超过 200ms,但是上游响应时间只有 20 毫秒左右(下图红圈中依次为:$request_time$status$body_bytes_sent$upstream_response_time)。也就是说这些请求在 Nginx 内部处理了超过 200ms,显然这不正常。

图4、Nginx 日志

0x03 深入排查

通过日志确定了是 Nginx 的原因,那就从 Nginx 上查起。既然偶现延迟,那就先看是否是系统资源不足导致的问题。

3.1 系统资源

系统资源的排查比较简单,登陆 Nginx 所在的机器,使用 top 等命令(或者使用监控)分析 CPU内存等资源。

图5、通过 top 命令查看系统资源

也可以通过 pidstat 命令指定查看 Nginx 进程的资源占用信息。

bash
复制代码ps -ef | grep nginx | grep -v grep | awk 'BEGIN {print "pidstat \\"} {print "-p "$2" \\"} END {print "1"}' | bash

图6、pidstat查看指定进程信息 通过排查,并未发现系统资源不足的情况。

3.2 通过日志排查原因

系统资源充足,只能从其它维度入手进行排查,既然延迟产生的频率不高,那有没有可能跟某一个其它指标相关联呢,如:上游服务、请求包大小等。这个可以通过日志入手,配合 awk 等命令进行统计。

bash复制代码# $12 为 $upstream_response_time
# $9 为 $request_time
# $11 为 $body_bytes_sent
awk '{duration=$9-$12; if (duration > 0.1) {print duration " " $11}}' /path/to/nginx/access.log

图7、延迟与包大小的关系

上图表明,延迟跟请求包大小没有关联,使用相同的办法统计延迟与上游服务、响应包大小等的关联数据,同样没有发现有任何关联关系。

3.3 抓包

一些简单、直接的排查方案没法确定问题,那就只能上大杀器:tcpdump 抓包了。其实 Nginx 延迟再高,也不至于超过 200ms,能让 Nginx 出现有如此高的延迟基本上也只有网络了。如果一开始就直接上抓包也是没有太大问题的。

直接在客户端和服务端使用 tcpdump 命令进行抓包:

bash复制代码# -s 指定抓取数据包中的字节数,0表示不限制
tcpdump -i eth0 -s0 '(host 1.2.3.4 or host 2.3.4.5)' -w /tmp/serverside.pcap

由于抓取到的是所有的请求包,要定位到某一个特定的请求会比较困难,好在请求 header 头中有一个 Request-Id 的头,在 Nginx 日志添加变量 $http_request_id 的输出,这样可以通过日志快速定位到某个请求:

图8、添加请求ID的 Nginx 日志

拿到请求ID,使用 tshark 命令过滤出包含请求ID的 TCP 连接。

bash
复制代码tshark -r 1228_10.pcap -Y 'http contains "44aaf9b83bb24fed9f81d3bfcc0af605"' -e tcp.stream -e http.request.full_uri -T fields

图9、查找包含请求ID的 TCP 连接

随后在 wireshark 中使用 tcp.stream==212487 即可过滤出对应的 TCP 连接。最终的抓包(左边客户端、右边服务端)如下:

图10、客户端和服务端抓包

我们分析一下右边服务端的抓包,主要问题在于 11 号和 12 号包顺序乱了,于是内核丢掉了两个包,并且发送了 13 号 ACK 包,告知客户端 6 号包之后的包没收到,客户端在等待 200ms 后重传 14 号 FIN 包。

💡 这里补充一个更详细的分析说明:6 号包是客户端传输到服务端的最后一个数据包,Seq=1413,Len=2238,那下一个传输包的 Seq 期望为:1413+2238=3651,可以看到 10 号 ACK 包(8号包的 ACK 包)Seq 为 3651,符合预期。但是 11 号包的 Seq 为 3652,多了1,不符合预期,12 号包 Seq 为 3651,两个包顺序交换后符合预期。内核在收到乱序的包后直接丢掉了11、12号包,然后 ACK 确认了 6 号包,让客户端重传6号包后面的包。客户端在等待 200ms 后依然未收到服务端发送的 FIN 包的 ACK 包,于是重传。

为什么这里没有触发 TCP 的快速重传(客户端快速重传)机制呢?因为快速重传要求收到三次冗余 ACK 才重传,但是 FIN 包属于客户端发的最后一个包了,后续不会再有包传过来,服务端也就不可能返回 ACK 包了。

0x04 根因及解决方案

定位到 200ms 延迟的原因,就要找是什么导致了包的乱序?这里就不卖关子了,通过拉网络和底层等同事的协同抓包排查,最终发现是服务端(虚拟机)的宿主机上的一个特性导致的,该特性为了加速网络包的处理,会将数据包和 ACK 包等使用硬件加速处理,但是 FIN 包等还是会走软件层处理,这就导致了交给上层虚拟机的顺序产生了变化。

那为什么只有部分包会出现这种情况呢?这是因为只有 FIN 包和 ACK 包几乎同时到达宿主机,才会因为处理设备的不同出现乱序,这种概率本身就比较低。

找到根因了应该如何处理呢?有两种方案:

  1. 更换没有此特性宿主机下的虚拟机。
  2. 在服务端 Nginx 配置中增加 lingering_close off 设置。

第一种比较好理解,第二种是什么方案呢?回到图 10 的服务端抓包,其实服务端在 8 号包(HTTP 响应)发送完毕之后已经完成了数据的发送(最后一个字节已经发送并 ACK 确认),按理说上层统计时长应该按照这个时间来统计,不应该等 TCP 断开后再统计时间。这个就涉及到 Nginx 的关闭方式了,它使用的是 shutdown 而非 close 来关闭 socket 的,两个区别如下:

  1. shutdown 表示单端关闭,TCP 为全双工,两端的收发互相对应,shutdown 可以选择一端关闭;如果关闭的发送端,内核的行为其实就是一个 FIN(暂不考虑linger关闭),关闭接收端其实就啥也没做。此时,socket 其实还是被进程持有的,进程仍然可以从 socket 中读取队列中缓存的包,但是此时不能再发了。
  2. close 是双端关闭,即已经收到的缓存包(未被APP读取的)也不要了。对应的 socket 连接,进程也不再关注了,后面的 FIN_WAIT/TIME_WAIT 都统统交给内核处理了,socket 可以认为是orphan socket 了。

也就是虽然 Nginx 关闭了连接,但是它依然持有此 socket,必须要等待此 socket 完全确认关闭才算关闭。而 lingering_close off 设置可以让 Nginx 使用 close 来关闭 socket 连接。带来的影响就是假如 10 号包之后,客户端还有数据包发送,Nginx 不会再接收处理了(Nginx 单方面关闭接收)。

为了减少影响,我们最终选择更换虚拟机。

0x05 总结

虽然之前曾解决过一些网络的问题,并且也熟悉 TCP 的 ACK 机制,但是始终浮于表面,在刚开始拿到抓包结果的时候,因为有 wireshark 的高亮提示,知道是乱序重传,但是为什么重传始终没有搞清楚。后面在整理复盘整个过程的中,重新审视抓包,认真对 Seq 和 Ack 的序号做了比对才终于理解为啥重传了,这也是我第三部份抓包图下补充的详细分析。后面学习还是要多多实践,不可纸上谈兵。

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL主从延迟问题数据库运维中经常遇到的问题之一,通常会引起应用程序性能下降或者数据不一致的问题。下面我们来介绍一下如何排查MySQL主从延迟问题。 1. 确认主从复制的状态 首先需要确认主从复制的状态。可以通过以下命令查看: ``` SHOW SLAVE STATUS \G; ``` 在输出结果中,需要关注以下几个参数: - Slave_IO_Running:表示Slave I/O线程是否正在运行,如果为YES,则正常运行,否则需要检查错误日志; - Slave_SQL_Running:表示Slave SQL线程是否正在运行,如果为YES,则正常运行,否则需要检查错误日志; - Seconds_Behind_Master:表示从库落后主库的时间,如果为0,则表示主从同步正常。 2. 确认网络延迟 如果主从复制状态正常,但是Seconds_Behind_Master不为0,那么需要确认网络延迟是否存在。可以通过在主库和从库上分别执行以下命令进行测试: 在主库执行: ``` SELECT NOW(); ``` 在从库执行: ``` SELECT NOW(); ``` 比较两个输出结果的时间差,即可得到主从复制的网络延迟时间。如果网络延迟较大,可以考虑优化网络连接或者将主从库部署在同一地域内。 3. 检查主从库配置 如果网络延迟不存在,那么需要检查主从库的配置是否正确。可以比较主从库的以下参数是否一致: - server_id:表示服务器的ID,主从库必须不同; - log_bin:表示二进制日志文件名称,主库必须开启二进制日志; - binlog_format:表示二进制日志格式,主库必须开启ROW格式; - replicate_do_db:表示从库需要复制的数据库,如果设置了该参数,则只复制该数据库; - replicate_ignore_db:表示从库忽略复制的数据库,如果设置了该参数,则不复制该数据库。 4. 检查主从库版本 如果主从库配置正确,但是主从延迟问题依然存在,那么需要检查主从库的版本是否一致。主从库的版本必须一致,否则会出现主从延迟问题。 5. 总结 通过以上步骤,可以排查MySQL主从延迟问题。在实际运维过程中,还可以通过监控工具对主从库的状态进行实时监控,及时发现主从延迟问题,并进行处理

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值