谈谈 TCP 的 TIME_WAIT

由来


最近有同事在用 ab 进行服务压测,到 QPS 瓶颈后怀疑是起压机的问题,来跟我借测试机,因而我就趁机分析了一波起压机可能成为压测瓶颈的可能,除了网络 I/O、机器性能外,还考虑到了网络协议的问题。git

固然本文的主角并非压测,后来分析证实同事果真仍是想多了,瓶颈是在服务端。github

分析起压机瓶颈的过程当中,对于 TCP TIME_WAIT 状态的一个猜测引发了个人兴趣。因为以前排查问题时,简单地接触过这个状态,但并未深刻了解,因而决定抽时间分析一下,拆解一下个人猜测。服务器

转载随意,文章会持续修订,请注明来源地址:https://zhenbianshu.github.io 。网络

TCP 的状态转换


咱们都知道 TCP 的三次握手,四次挥手,说来简单,但在不稳定的物理网络中,每个动做都有可能失败,为了保证数据被有效传输,TCP 的具体实现中也加入了不少对这些异常情况的处理。并发

状态分析

先用一张图来回想一下 TCP 的状态转换。curl

一眼看上去,这么多种状态,各个方向的连线,让人感受有点懵。但细细分析下来,仍是有理可循的。socket

首先,整个图能够被划分为三个部分,即上半部分建连过程,左下部分主动关闭链接过程和右下部分被动关闭链接过程。tcp

再来看各个部分:建连过程就是咱们熟悉的三次握手,只是这张图上多了一个服务端会存在的 LISTEN 状态;而主动关闭链接和被动关闭链接,都是四次挥手的过程。高并发

查看链接状态

在 Linux 上,咱们经常使用 netstat 来查看网络链接的状态。固然咱们还可使用更快捷高效的 ss (Socket Statistics) 来替代 netstat。工具

这两个工具都会列出此时机器上的 socket 链接的状态,经过简单的统计就能够分析出此时服务器的网络状态。

TIME_WAIT


定义

咱们从上面的图中能够看出来,当 TCP 链接主动关闭时,都会通过 TIME_WAIT 状态。并且咱们在机器上 curl 一个 url 建立一个 TCP 链接后,使用 ss 等工具能够在必定时长内持续观察到这个连续处于 TIME_WAIT 状态。

因此TIME_WAIT 是这么一种状态:TCP 四次握手结束后,链接双方都再也不交换消息,但主动关闭的一方保持这个链接在一段时间内不可用。

那么,保持这么一个状态有什么用呢?

缘由

上文中提到过,对于复杂的网络状态,TCP 的实现提出了多种应对措施,TIME_WAIT 状态的提出就是为了应对其中一种异常情况。

为了理解 TIME_WAIT 状态的必要性,咱们先来假设没有这么一种状态会致使的问题。暂以 A、B 来代指 TCP 链接的两端,A 为主动关闭的一端。

  • 四次挥手中,A 发 FIN, B 响应 ACK,B 再发 FIN,A 响应 ACK 实现链接的关闭。而若是 A 响应的 ACK 包丢失,B 会觉得 A 没有收到本身的关闭请求,而后会重试向 A 再发 FIN 包。

    若是没有 TIME_WAIT 状态,A 再也不保存这个链接的信息,收到一个不存在的链接的包,A 会响应 RST 包,致使 B 端异常响应。

    此时, TIME_WAIT 是为了保证全双工的 TCP 链接正常终止。

  • 咱们还知道,TCP 下的 IP 层协议是没法保证包传输的前后顺序的。若是双方挥手以后,一个网络四元组(src/dst ip/port)被回收,而此时网络中还有一个迟到的数据包没有被 B 接收,A 应用程序又马上使用了一样的四元组再建立了一个新的链接后,这个迟到的数据包才到达 B,那么这个数据包就会让 B 觉得是 A 刚发过来的。

    此时, TIME_WAIT 的存在是为了保证网络中迷失的数据包正常过时。

由以上两个缘由,TIME_WAIT 状态的存在是很是有意义的。

时长的肯定

由缘由来推实现,TIME_WAIT 状态的保持时长也就能够理解了。肯定 TIME_WAIT 的时长主要考虑上文的第二种状况,保证关闭链接后这个链接在网络中的全部数据包都过时。

说到过时时间,不得不提另外一个概念: 最大分段寿命(MSL, Maximum Segment Lifetime),它表示一个 TCP 分段能够存在于互联网系统中的最大时间,由 TCP 的实现,超出这个寿命的分片都会被丢弃。

TIME_WAIT 状态由主动关闭的 A 来保持,那么咱们来考虑对于 A 来讲,可能接到上一个链接的数据包的最大时长:A 刚发出的数据包,能保持 MSL 时长的寿命,它到了 B 端后,B 端因为关闭链接了,会响应 RST 包,这个 RST 包最长也会在 MSL 时长后到达 A,那么 A 端只要保持 TIME_WAIT 到达 2MS 就能保证网络中这个链接的包都会消失。

MSL 的时长被 RFC 定义为 2分钟,但在不一样的 unix 实现上,这个值不并肯定,咱们经常使用的 centOS 上,它被定义为 30s,咱们能够经过 /proc/sys/net/ipv4/tcp_fin_timeout 这个文件查看和修改这个值。

ab 的”奇怪”表现


猜测

由上文,咱们知道因为 TIME_WAIT 的存在,每一个链接被主动关闭后,这个链接就要保留 2MSL(60s) 时长,一个网络四元组也要被冻结 60s。而咱们机器默承认被分配的端口号约有 30000 个(可经过 /proc/sys/net/ipv4/ip_local_port_range文件查看)。

那么若是咱们使用 curl 对服务器请求时,做为客户端,都要使用本机的一个端口号,全部的端口号分配到 60s 内,每秒就要控制在 500 QPS,再多了,系统就没法再分配端口号了。

但是在使用 ab 进行压测时时,以每秒 4000 的 QPS 运行几分钟,起压机照样正常工做,使用 ss 查看链接详情时,发现一个 TIME_WAIT 状态的链接都没有。

分析

一开始我觉得是 ab 使用了链接复用等技术,仔细查看了 ss 的输出发现本地端口号一直在变,究竟是怎么回事呢?

因而,我在一台测试机启动了一个简单的服务,端口号 8090,而后在另外一台机器上起压,并同时用 tcpdump 抓包。

结果发现,第一个 FIN 包都是由服务器发送的,即 ab 不会主动关闭链接。

登上服务器一看,果真,有大量的 TIME_WAIT 状态的链接。

可是因为服务器监听的端口会复用,这些 TIME_WAIT 状态的链接并不会对服务器形成太大影响,只是会占用一些系统资源。

小结


固然,高并发状况下,太多的 TIME_WAIT 也会给服务器形成很大的压力,毕竟维护这么多 socket 也是要消耗资源的,关于如何解决 TIME_WAIT 过多的问题,能够看 tcp短链接TIME_WAIT问题解决方法大全(1)——高屋建瓴

多了解原理遇到问题才能更快地找到根源解决,网络相关的知识还要继续巩固啊。

关于本文有什么疑问能够在下面留言交流,若是您以为本文对您有帮助,欢迎关注个人 微博 或 GitHub 。您也能够在个人 博客REPO 右上角点击 Watch 并选择 Releases only 项来 订阅 个人博客,有新文章发布会第一时间通知您。

相关文章
相关标签/搜索