首页 > 运维 > TCP半连接队列和全连接队列满了会发生什么?又该如何应对
2021
11-13

TCP半连接队列和全连接队列满了会发生什么?又该如何应对

提纲

图片.png


什么是 TCP 半连接队列和全连接队列?

在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

    半连接队列,也称 SYN 队列;

    全连接队列,也称 accepet 队列;

服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK,接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 accept 队列,等待进程调用 accept 函数时把连接取出来。



不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,内核会直接丢弃,或返回 RST 包。


实战 - TCP 全连接队列溢出


    如何知道应用程序的 TCP 全连接队列大小?


在服务端可以使用 ss 命令,来查看 TCP 全连接队列的情况:

但需要注意的是 ss 命令获取的 Recv-Q/Send-Q 在「LISTEN 状态」和「非 LISTEN 状态」所表达的含义是不同的。

# ss -lnt
State      Recv-Q Send-Q Local Address:Port               Peer Address:Port              
LISTEN     0      128    127.0.0.1:6379                     *:*                  
LISTEN     0      128    127.0.0.1:5200                     *:*

在「LISTEN 状态」时,Recv-Q/Send-Q 表示的含义如下:

    Recv-Q:当前全连接队列的大小,也就是当前已完成三次握手并等待服务端 accept() 的 TCP 连接个数;

    Send-Q:当前全连接最大队列长度,上面的输出结果说明监听 5200 端口的 TCP 服务进程,最大全连接长度为 128;

# ss -nt 
State      Recv-Q Send-Q Local Address:Port               Peer Address:Port     
         
ESTAB      0      0      10.10.2.16:60462              10.10.2.11:9101          
     
ESTAB      0      0      10.10.2.16:33614              10.10.2.11:9101

在「非 LISTEN 状态」时,Recv-Q/Send-Q 表示的含义如下:

    Recv-Q:已收到但未被应用进程读取的字节数;

    Send-Q:已发送但未收到确认的字节数;


如何模拟 TCP 全连接队列溢出的场景?


这里先介绍下 wrk 工具,它是一款简单的 HTTP 压测工具,它能够在单机多核 CPU 的条件下,使用系统自带的高性能 I/O 机制,通过多线程和事件模式,对目标机器产生大量的负载。客户端执行 wrk 命令对服务端发起压力测试,并发 3 万个连接:


图片.png

在服务端可以使用 ss 命令,来查看当前 TCP 全连接队列的情况:

图片.png

其间共执行了两次 ss 命令,从上面的输出结果,可以发现当前 TCP 全连接队列上升到了 129 大小,超过了最大 TCP 全连接队列。

当超过了 TCP 最大全连接队列,服务端则会丢掉后续进来的 TCP 连接,丢掉的 TCP 连接的个数会被统计起来,我们可以使用 netstat -s 命令来查看:

图片.png

上面看到的 41150 times ,表示全连接队列溢出的次数,注意这个是累计值。可以隔几秒钟执行下,如果这个数字一直在增加的话肯定全连接队列偶尔满了。

从上面的模拟结果,可以得知,当服务端并发处理大量请求时,如果 TCP 全连接队列过小,就容易溢出。发生 TCP 全连接队溢出的时候,后续的请求就会被丢弃,这样就会出现服务端请求数量上不去的现象。



全连接队列满了,就只会丢弃连接吗?

实际上,丢弃连接只是 Linux 的默认行为,我们还可以选择向客户端发送 RST 复位报文,告诉客户端连接已经建立失败。

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

tcp_abort_on_overflow 共有两个值分别是 0 和 1,其分别表示:

    0 :表示如果全连接队列满了,那么 server 扔掉 client  发过来的 ack ;

    1 :表示如果全连接队列满了,那么 server 发送一个 reset 包给 client,表示废掉这个握手过程和这个连接;

如果要想知道客户端连接不上服务端,是不是服务端 TCP 全连接队列满的原因,那么可以把 tcp_abort_on_overflow 设置为 1,这时如果在客户端异常中可以看到很多 connection reset by peer 的错误,那么就可以证明是由于服务端 TCP 全连接队列溢出的问题。

通常情况下,应当把 tcp_abort_on_overflow 设置为 0,因为这样更有利于应对突发流量。

举个例子,当 TCP 全连接队列满导致服务器丢掉了 ACK,与此同时,客户端的连接状态却是 ESTABLISHED,进程就在建立好的连接上发送请求。只要服务器没有为请求回复 ACK,请求就会被多次重发。如果服务器上的进程只是短暂的繁忙造成 accept 队列满,那么当 TCP 全连接队列有空位时,再次接收到的请求报文由于含有 ACK,仍然会触发服务器端成功建立连接。

所以,tcp_abort_on_overflow 设为 0 可以提高连接建立的成功率,只有你非常肯定 TCP 全连接队列会长期溢出时,才能设置为 1 以尽快通知客户端。


TCP全链接队列满的问题分析和解决方案我们可以参考下面这篇文章

https://www.phpmianshi.com/?id=323



TCP 半连接队列满实战: https://www.phpmianshi.com/?id=384

本文》有 0 条评论

留下一个回复