由来

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

当然本文的主角并不是压测,后来分析证明同事果然还是想多了,瓶颈是在服务端。

分析起压机瓶颈的过程中,对于 TCP TIME_WAIT 状态的一个猜想引起了我的兴趣。由于之前排查问题时,简单地接触过这个状态,但并未深入了解,于是决定抽时间分析一下,拆解一下我的猜想。

TCP 的状态转换

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

状态分析

先用一张图来回想一下 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 过多的问题,后续会有相关的文章介绍,请持续关注 民工哥技术之路 公众号的文章。

来源:枕边书原文:http://t.cn/EiRm6Qp

关注 民工哥技术之路 微信公众号对话框回复关键字:1024 可以获取一份最新整理的技术干货。

tcp port numbers reused出现原因_谈谈 TCP 的 TIME_WAIT相关推荐

  1. tcp port numbers reused出现原因_python socket(tcp 线程)实现简单聊天室

    一.原理说明 框架代码说明: #python通过线程和socket 最简单的方式实现TCP聊天功能import socketimport threadingip='127.0.0.1'port=777 ...

  2. tcp retransmission 出现的原因_为什么 TCP 会被 UDP 取代?

    TCP 协议可以说是今天互联网的基石,作为可靠的传输协议,在今天几乎所有的数据都会通过 TCP 协议传输,然而 TCP 在设计之初没有考虑到现今复杂的网络环境,当你在地铁上或者火车上被断断续续的网络折 ...

  3. 为什么tcp不采用停等协议_为什么 TCP 协议有粘包问题

    来自公众号:真没什么逻辑 链接:https://draveness.me/whys-the-design-tcp-message-frame/ 为什么这么设计(Why's THE Design)是一系 ...

  4. 简述tcp协议三报文握手过程_简述TCP的三次握手过程

    TCP握手协议 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接. 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器 ...

  5. 为什么tcp不采用停等协议_为什么TCP建立连接协议是三次握手,而关闭连接却是四次握手呢?...

    看到了一道面试题:"为什么TCP建立连接协议是三次握手,而关闭连接却是四次握手呢?为什么不能用两次握手进行连接?",想想最近也到金三银四了,所以就查阅了相关资料,整理出来了这篇文章 ...

  6. zynq tcp如何从网口发数据_基于TCP/IP协议的电口通信

    之前有介绍过TCP/IP协议的实现是通过轻量级LWIP协议实现的,具体在FPGA中实现又可以分为多种方式,具体如下: 图8‑98 LWIP协议在FPGA中的实现方式 LWIP可以通过硬核实现或者软核实 ...

  7. close wait 过多原因_干货分享!服务端 TCP 连接的 TIME_WAIT 问题分析与解决

    写在开头,大概 4 年前,听到运维同学提到 TIME_WAIT 状态的 TCP 连接过多的问题,但是当时没有去细琢磨:最近又听人说起,是一个新手进行压测过程中,遇到的问题,因此,花点时间,细深究一下. ...

  8. 【翻译自mos文章】OGG replicat 进程使用的 TCP port

    OGG replicat 进程使用的 TCP port 来源于: TCP PORT USED BY REPLICAT PROCESSES (文档 ID 1060954.1) 适用于: Oracle G ...

  9. Java JDBC连接SQL Server2005错误:通过port 1433 连接到主机 localhost 的 TCP/IP 连接失败...

    错误原因例如以下: Exception in thread "main" org.hibernate.exception.JDBCConnectionException: Cann ...

最新文章

  1. 优秀好文收录(持续更新...)
  2. 读债务危机0812:接管房利美和房地美
  3. Linux 服务器中文乱码编码解决
  4. hadooppythonudf_Hive使用python编写的自定义函数UDF进行ETL
  5. bzoj 1562 [NOI2009]变换序列 二分图
  6. 观视屏《残疾人郑心意》所想
  7. HTML中ul等标签的用法
  8. Sql Server 性能优化之包含列
  9. 自带flash的浏览器_受够了手机自带浏览器?来看看这些超实用的不常用浏览器...
  10. DB9 公头母头引脚定义及连接、封装
  11. 海力士固态测试软件,【海力士 256G MSATA固态硬盘使用总结】性能|接口|数据|品牌_摘要频道_什么值得买...
  12. win10怎么卸载Edge浏览器
  13. WPS正式推出了JS宏(WPS宏编辑器)如何切换会传统VB环境
  14. 《线粒体疾病的遗传》学习笔记
  15. REPL module
  16. Kafka Topic分区手动迁移:kafka-reassign-partitions
  17. 如果不懂 numpy,请别说自己是 python 程序员
  18. Openstack 发行版本列表
  19. cadence allegro - E
  20. 数据解读热门美剧 | 《权力的游戏》花式死亡图鉴

热门文章

  1. ubuntu18.04下利用deepin-wine-wechat安装微信显示问题
  2. pandas中inplace_pandas中inplace参数
  3. python paramiko sftp_python paramiko (ssh,sftp)
  4. c语言如何用fscanf将字符串读取,在c语言中如何将文本内容 赋给一个 字符串
  5. Java的setmargin,Java Sheet.setMargin方法代碼示例
  6. 每日程序C语言6-判断某范围之间的素数
  7. code ./打不开vscode编辑器
  8. linux系统分区不,其中,不属于Linux系统分区的是()。
  9. 关于可迭代对象、迭代器和生成器
  10. Hadoop_计算框架MapReduce