高并发服务中的TCP异常排查

737 阅读5分钟

一 简介

广大码农朋友在开发网络程序的时候,有时候会遇到客户端的请求较慢的情况,但是这个时候从服务的角度看每个请求的处理时间又很短,那么这是什么问题呢。整体网络的传输的都有哪些步骤呢?又有哪些核心的结构?经常出现的问题是何原因?

笔者在早些年开发大量并发的服务的时候,经常会进行压测会出现客户端异常的情况,虽然知道是因为服务端过载造成,但是对于具体的本质原因确实缺乏探索,所以本文进行了一下整个TCP过程的跟踪与分析。

二 Tcp的三次握手与四次挥手过程

所谓三次握手(Three-way Handshake),顾名思义,是指在建立一个TCP连接时,客户端和服务器之间总共需要发送三个数据包,进行协商确认。

三次握手的目的是通过连接服务器指定端口,建立TCP连接。同时通过同步连接双方的序列号、确认号、交换 TCP 窗口大小等信息,以保障整个链路的数据的完整性与时效性。在socket编程中,客户端执行connect()时。将触发三次握手。

所谓四次挥手,TCP的连接的终止,需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。

为何三次握手,但是挥手是四次呢?

握手过程:服务端的LISTEN状态下,当收到SYN报文的连接请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。 但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给了;但未必本侧所有的数据都全部发送给对方了,所以本次应用程序未必会马上会关闭SOCKET,调用close。所以这里的ACK报文和FIN报文多数情况下都是分开发送的。也就是说,当握手的时候connect一个就可以决定,只要一方发起双方就建立连接。但是对于挥手来说,需要双方都进行close操作。

在这里插入图片描述

三 server端的队列

在这里插入图片描述

服务器维护一个半连接队列,在三次握手协议中,该队列为每个客户端的SYN包开设一个条目(服务端在接收到SYN包的时候,就已经创建了request_sock结构,存储在半连接队列中),该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包(会进行第二次握手发送SYN+ACK 的包加以确认,这个在稍后会讲解)。这些条目所标识的连接在服务器处于SYN_RECV状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态。

server端的半连接队列(SYN 队列)

  • 长度为 max(64,tcp_max_syn_backlog) ,
  • tcp_max_syn_backlog值在/proc/sys/net/ipv4/tcp_max_syn_backlog下配置。

server端的完全连接队列(accpet队列)

当第三次握手时,当server接收到ACK 报之后, 会进入一个新的叫 accept 的队列,accept队列长度

  • 该队列的长度为 min(backlog, somaxconn)
  • 默认情况下,somaxconn 的值为 128,表示最多有 129 的 ESTAB 的连接等待 accept(),查看地址/proc/sys/net/core/somaxconn
  • backlog 的值则应该是由 int listen(int sockfd, int backlog) 中的第二个参数指定,listen 里面的 backlog 可以有我们的应用程序去定义的。

accept队列满的处理

  • 通过命令 ss -s可以查看,例如

      698399 times the listen queue of a socket overflowed
    

反复使用该命令

	watch -n 1 "ss -s"

如果发现overflowed一直在增加,那么可以确定全连接队列是溢出了。

  • 溢出后的处理,会拒绝发送syn+ack包给client,但是会隔一段时间server重发握手第二步的syn+ack包给client,重发多少次由/proc/sys/net/ipv4/tcp_synack_retries决定),但是如果这个连接一直排不上队就异常了,会报“connection reset by peer”。

      # cat /proc/sys/net/ipv4/tcp_abort_on_overflow
      0
      # cat /proc/sys/net/ipv4/tcp_synack_retries
      5
    

tcp_abort_on_overflow 为0表示如果三次握手第三步的时候全连接队列满了那么server扔掉client 发过来的ack(在server端认为连接还没建立起来)

为了证明客户端应用代码的异常跟全连接队列满有关系,先把tcp_abort_on_overflow修改成 1,1表示第三步的时候如果全连接队列满了,server发送一个reset包给client,表示废掉这个握手过程和这个连接(本来在server端这个连接就还没建立起来),

接着测试然后在客户端异常中可以看到很多connection reset by peer的错误,到此证明客户端错误是这个原因导致的。

四 总结

经过上面的分析,可以明确的是全连接队列满了导致。 这个时候有2个解决方案:

  • 如果backlog已经设置较大,看看客户端的并发是不是太大导致,这个时候需要水平扩展机器;
  • 如果设置过小,可以尝试调账backlog队列的长度进行尝试。