三次握手

服务端通过系统调用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