Tcp三次握手与Backlog
Contents
[NOTE] Updated August 27, 2023. This article may have outdated content or subject matter.
三次握手
服务端通过系统调用bind()
和listen()
绑定和监听socket-port,此时服务端处于 LISTEN 状态,当客户端调用 connect()
向服务端发起连接时,就进入到我们熟悉的三次握手当中。
- 客户端向服务端发送SYN报文,同时TCP连接处于
SYN_SEND
状态 - 服务端收到SYN报文后,会向客户端发送SYN+ACK报文,同时服务端TCP连接处于
SYN_RECV
状态 - 客户端收到SYN+ACK报文后,会再次向服务端发送ACK报文确认连接建立,同时客户端TCP连接状态变为
ESTABLISHED
- 服务端收到客户端的再次确认后,TCP连接也将变为ESTABLISHED
全连接队列与半连接队列
在三次握手过程中,服务端通过两个数据结构来维护连接状态,即syns queue (半连接队列)和accept queue (全连接队列)
- 当服务端收到SYN报文后,会将该连接信息放到syns queue中
- 当服务端收到ACK报文后,会将这个连接从syns queue中转移到accept queue中,此时TCP连接建立完成
- 处于accept queue中的连接,服务端还要通过系统调用accept()把该套接字取走,才能进行客户端到服务端的数据传输
backlog
backlog用来限制全连接队列的大小,对应Linux内核参数 somaxconn,min(backlog,somaxconn)就是全连接队列的最大容量限制,两个值分别由用户和内核设置。顺带一提这也是为什么redis在某些机器上运行报错的原因,因为redis.conf设置了tcp-backlog是511。
半连接队列也有大小限制,对应Linux内核参数tcp_max_syn_backlog,实际大小取决于三者最小值。
当客户端发送SYN报文,而此时半连接队列已满时就会丢弃,客户端会进行 tcp_syn_retries 次的重试,或者到达应用层时间限制,返回 connection timeout 的异常。
当服务端收到第三次握手的 ACK 时,若全连接队列已满,会根据 tcp_abort_on_overflow 选择是抛弃ack还是发RST。如果抛弃ack了,类似的进行 tcp_synack_retries。
相关Linux的网络异常问题
- SYN-FLOOD:打满第一次握手的半连接队列的限制容量。
- backlog过小,无法建立连接,导致502 bad gateway的错误
- backlog过大,处理跟不上,导致超时,504
排查工具
netstat或ss
- ss -lnt,若连接处于LISTEN状态,Recv-Q、Send-Q分别代表了全连接队列的连接数、容量;非LISTEN状态Recv-Q是缓冲区还未被应用程序取走的字节数,Send-Q表示还未被远端确认的字节数
- netstat -s可以看统计信息
调优策略
- 高并发场景增大backlog
- 最好QPS=backlog,以免服务超时关闭,进一步导致 broken pipe