虽然高级语言或者网络库对于网络连接的细节进行了屏蔽, 但是在遇到瓶颈的时候难免要深入其中,才能对其进行调优. 那么连接建立和终止的时候发生了什么?
三次握手和四次挥手
两个机器需要传输数据,首先要通过三次握手建立连接,然后再进行数据传输 , 断开后通过四次挥手确认断开连接.流程如下图
TCP连接建立的时候至少在客服端和服务器经过三次数据分节的交换,因此也被称之为三次握手.
首先服务器端 创建socket对象并绑定和侦听端口
客户端开启socket连接 调用connect , 发送 SYN 分节数据 seq = x
服务器端接收到SYN分节数据后 , 回复 SYN seq=y 和 ACK x+1
客户端在收到服务器端数据后 , 回复ACK y+1 , 连接建立成功
连接建立后即可传输数据, 当数据传输完毕后.需要终止连接. 终止一个连接需要4个分节的数据交换.图中假设是客户端主动进行关闭的.
客户端调用 close以后, 发送FIN分节数据 , Fin seq=x+2 ACK=y+1
服务器端接收到FIN分节数据, 称为被动关闭 ,此时服务器端同意关闭发送 ACK x+3
过一段时间后 , 服务器端已无在发送到客户端的数据 , 则调用close来关闭套接字,此时它发送给客户端FIN分节数据 FIN seq=y+1
客户端接收到FIN分节数据后, 确认这个FIN , 发送 ACK=y+2
连接队列
三次握手环节比较关注的SYN_RCVD和ESTABLISHED.服务器端维护SYN_RCVD的半连接队列和ESTABLISHED的全连接队列.在被accept之前的连接都是存放在这连个队列里面.
队列大小
SYN队列大小可以使用使用以下命令来查看, 线上的机器一般会调整得比较大
全连接队列大小等于 min(backlog, somaxconn) . backlog是在socket创建的时候传入的,somaxconn是os的一个参数
Java ServerSocket 默认的backlog参数是50.如下代码 , 启动程序后使用命令看到 Send-Q 为50
如果调整下参数,指定backlog大小,则会是另外一种现象
队列溢出
backlog设置比较小的时候导致的溢出,从机器负载的监控或者观察中很难看出来.如果机器指标没有问题而吞吐一直上不去则可以使用如下命令查看是否是队列溢出了.
短时间内溢出的数量有比较大的增长,调大backlog的值可以提升吞吐.
TIME_WAIT状态
四次挥手中, 比较关心的是TIME_WAIT状态 . 这个状态在高并发短连接的场景上也比较容易出问题. 原因是在于与客户端通信完成后,服务器端主动关闭连接 , TIME_WAIT状态要在2MSL的时间后才能转变成CLOSED状态,此时端口才能被回收. 而网络端口范围在0~65535的范围 . 高并发下 业务处理+传输数据的时间 远远小于 TIME_WAIT超时的时间,端口很快就会被消耗完.从这个角度看长连接可以比较少考虑这个状态的问题.
这是在一个http 服务器上跑出的结果,几个并发比较高的程序都占用了几千的端口. 这还算是比较正常的情况.
如何避免
后端之间尽量使用长连接 , 如在nginx反向代理的情况下可以增加keepalive的时间和长连接请求次数
如果无法解决可以调整系统参数 , 尽量快地回收TIME_WAIT状态下的端口
领取专属 10元无门槛券
私享最新 技术干货