close wait 过多原因_高并发下的TIME_WAIT

更多总结mysql、Redis、网络编程、golang、高并发方案

高并发感觉是检验程序员一个很好的标准。经常写业务的人员,如果写的高并发业务,他底层知识一般也不会差,因为遇到过几次高并发下的事故后,排查后也会逐步被动完善底层知识。

例如在高并发下,发现个别服务器可用性降低。排查业务代码后也没有发现任何异常。最后登录异常实例的机器,才发现有特别大量TIME_WAIT的状态。

TIME_WAIT的危害

在高并发短连接的TCP服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于TIME_WAIT状态。

第一是内存资源占用,这个目前看来不是太严重,基本可以忽略。 第二是对端口资源的占用,一个 TCP 连接至少消耗一个本地端口。要知道,端口资源也是有限的,一般可以开启的端口为 32768~61000 ,也可以通过net.ipv4.ip_local_port_range指定,如果 TIME_WAIT 状态过多,会导致无法创建新连接。这个也是我们在一开始讲到的那个例子。

扩展知识

我服务监听的是80端口啊,服务端在主动关闭后,会占用这么多端口呢?不应该只是80这一个端口吗什么会涉及到端口被占满?

端口作用

互联网上各主机间通过TCP/IP协议发送和接收数据包,各个数据包根据其目的主机的ip地址来进行互联网络中的路由选择。ip解决了主机之间的传输。
但是主机上都会支持多进程/线程,那么这个数据包需要发给哪个线程呢?这个就需要端口了。系统会给每个进程/线程分配一个端口号,这样数据包就能根据端口号找到指定的进程/线程。

socket

socket是7层网络协议的传输层,每一个任务(进程/线程),和对端建立传输时,都是通过socket建立的。
socket 上面包含五元组信息

  1. 源IP
  2. 源端口
  3. 目的IP
  4. 目的端口
  5. 类型:TCP or UDP

这个五元组,即标识了一条可用的连接。

基于上面知识,可以回答这个问题了。80端口是映射到外网的一个出口端口,当程序监听80端口,收到一个连接时,会创建一个 socket 传输数据包,创建socket是,系统就会分配一个端口号。

TIME_WAIT有什么用

如果TIME_WAIT的危害这么大,那为什么还需要他呢?
这个就需要从TCP的四次挥手说起了。同时我们也聊聊TCP的三次握手。

之前在提到3次握手,也都感觉仅仅是面试时需要准备下的,其实他在网络通信中,还是比较重要的。

首先简单说下,为什么TCP 需要三次握手和四次挥手,因为TCP协议都是建立在默认网络是不好的情况下,他不相信自己发出的信息对方一定能够收到,这个时候,就需要对方回复确认收到的信息,才能安心。
同时TCP面对网络也有很复杂的拥塞控制,防止丢包等等涉及。

先说说和本文相关的四次挥手,这个懂了,建立连接时的三次握手也就更好理解了。

四次挥手

74fc191c44738f176a9459f47fba92d3.png

这个是四次挥手的流程图:

第一次:
主机1发送关闭并且进入ESTABLISHED 状态变为 FIN_WAIT_1

第二次:
主机2收到结束消息,立刻返回结束的响应,并且改变状态ESTABLISHED 状态变为 CLOSE_WAIT。
主机1收到消息,也只是知道2收到消息的,FIN_WAIT_1 改成 FIN_WAIT_2。

第三次:
主机2在收到消息后,不能立刻结束任务,socket是有缓存的,结束的消息不可能实时处理,当主机2知道应用程序通过系统函数read读到socket的EOF后(这个时候,证明服务以及知道要结束了),会发送一个FIN 的消息给主机一,表示可以真的结束了。
并且状态从CLOSE_WAIT 变为 LAST_ACK。

第四次:
主机1收到主机2真正结束的消息后,也会发生一个反馈告知自己收到了,就从FIN_WAIT_2 变成了 TIME_WAIT(我们这次的主角)。
主机2 收到消息后,就能安心的结束自己的这个socket了。

最后:
主机 1 在 TIME_WAIT 停留持续时间是固定的(Linux默认是60秒),是最长分节生命期 MSL(maximum segment lifetime)的两倍,一般称之为 2MSL。过了这个时间之后,主机 1 就进入 CLOSED 状态。

TIME_WAIT能马上进入CLOSED吗?

  1. 确保最后的 ACK 能让被动关闭方接收
    TCP在设计的时候,有充分的容错性,如果报文出错,是需要可以重传的。如果TIME_WAIT马上进入CLOSED,这个时候,发现最后的一个ack传输失败,它就失去了当前状态的上下文,只能回复一个 RST 操作,从而导致被动关闭方出现错误。如果能够保留TIME_WAIT 这个状态,就能正确的发起重试。
  2. 连接“化身”和报文迷走
    为了让旧连接的重复分节在网络中自然消失。在原连接中断后,又重新创建了一个原连接的“化身”,说是化身其实是因为这个连接和原先的连接四元组完全相同,如果迷失报文经过一段时间也到达,那么这个报文会被误认为是连接“化身”的一个 TCP 分节,这样就会对 TCP 通信产生影响。TCP 就设计出了这么一个机制,经过 2MSL 这个时间,足以让两个方向上的分组都被丢弃,使得原来连接的分组在网络中都自然消失,再出现的分组一定都是新化身所产生的。

TIME_WAIT如何优化

优化的方案比较多,常用的是修改系统文件相关的参数。还有就是利用:net.ipv4.tcp_tw_reuse 到达一种可复用状态。
这种优化其实真实情况下,需要多结合实际的业务去处理。

个人比较建议的优化是,尽量都采用长连接的模式,避免短连接。例如golang的原生http包也都是默认支持长连接的。

长连接需要考虑不可控的情况是服务针对外部设备,比如100W的用户设备(浏览器)同时访问服务端,这个时候,确实是需要在考虑长连接维护这个问题的。

不过,针对这种问题,一般的服务架构,不会把业务模块直接对外暴露,会有一个专门的接入层。
例如百度的bfe,这个接入层会维护管理上百万的长连接关系,有专家级别的人会维护,然后和下游服务连接的也是有限的可控的bfe实例。

所以理论上,如果不是专门维护接入层的同学,其实可以暂时忽略长连接太多的情况。

长连接相关可以参考网络编程_HTTP长连接

三次握手

把比较复杂的四次挥手弄懂了,会发现三次握手其实就比较简单了

08d379a8ef6643348ac4b05a7913e0eb.png

在客户端发起三次握手前,服务端会先创建socket,绑定一个具体的ip,port。然后通过listen指令以阻塞的方式,监听绑定端口的响应。类似于告诉系统我这个socket是用来等待用户请求的。

客服端发起请求时,就会开始三次握手:
第一次:
发送一个消息给服务端,表示我要连接了,服务端收到消息后,开始调用accept,准备和客户端开始通信(这个时候,操作系统就和监听的应用程序建立起了连接)

第二次:
服务端阻塞方式调用accept后,回复一个好的,我接受连接了,客户端就知道服务端简历连接成功,进入ESTABLISHED 状态

第三次:
客户端ESTABLISHED后,告诉服务端,我建立好了,我们可以开始通信了。服务端收到后,服务器端协议栈使得 accept 阻塞调用返回,也进入 ESTABLISHED 状态。

扩展

服务器发现很多 CLOSE_WAIT是什么原因

应用程序没有及时响应对端的关闭。一般是程序bug导致,程序收到对端关闭后,没有正常关闭。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值