TCP queue 及 Nginx相关调优

本文阅读量 Posted by Kird on 2019-11-01
TCP Listen Queue and Nginx Tuning ..

How TCP Sockets Work

TCP socket如何工作,可以参考以下几篇文章:

TCP queue

SYN Queue

服务端接收到SYN报文,将收到的请求放在SYN queue中,等发送完SYN+ACK后,等待客户端回最后一次ACK建立三次握手,成功后请求放到Accept queue中。SYN queue也称半连接队列,Accept queue叫完全连接队列

Accpet Queue

完全三次握手的请求保存在完全连接队列,当应用程序使用**accept()**接受请求后,该请求从完全连接队列中清除并被送到应用程序中。

Queue size 大小限制

半连接队列大小max(sysctl -a|grep net.ipv4.tcp_max_syn_backlog ,64)
net.ipv4.tcp_syncookies 参数为1时,理论逻辑半连接队列无上限,不受此参数影响

全连接队列的最大长度根本上是有listen函数定义时的size大小决定的,如listen(sfd, 1024)定义全连接队列大小限制为1024个,这个参数由应用程序backlog和系统参数net.core.somaxconn取最小值决定,即min( backlog, sysctl -a| net.core.somaxconn) ,如Nginx默认backlog为511,如果不在listen指令显式配置backlog=NUM,及时系统参数tcp_max_syn_backlog及netdev_max_backlog均调整到很大,也使用默认511作为queue size;

listen man page:

1
2
3
4
5
The behavior of the backlog argument on TCP sockets changed with Linux 2.2.  Now it specifies the queue length
for completely established sockets waiting to be accepted, instead of the number of incomplete connection
requests. The maximum length of the queue for incomplete sockets can be set using
/proc/sys/net/ipv4/tcp_max_syn_backlog. When syncookies are enabled there is no logical maximum length and
this setting is ignored. See tcp(7) for more information.

如果超出队列大小怎么办?

如果全连接队列满,会出现:进入半连接队列的SYN包将会被丢弃,进入半连接队列的ACK包将会被丢弃。以下详细讲两种现象:

  • /proc/sys/net/ipv4/tcp_abort_on_overflow 参数决定超出队列的请求如何返回,0 表示直接丢弃(这里看资料测试过不是全部丢弃,而是随机丢弃SYN),客户端会超时重新发送SYN,直到达到客户端的连接等待超时时间,报错:Connection timeout1 表示丢弃后,发送RST通知客户端重置连接,客户端报错connection reset by peer;且服务端忽略ACK报文后会根据/proc/sys/net/ipv4/tcp_synack_retries参数进行SYN+ACK报文的进行重发,重复次数由/proc/sys/net/ipv4/tcp_synack_retries决定,默认为5次,也就是(1+2+4+8+16+32=61)s,不过这时候客户端早已经超时了吧。

  • 如果全连接队列满,半连接队列积攒过多,如果开启net.ipv4.tcp_syncookies=1,会系统出现如下报错,这种情况可能是全连接队列设置过小导致的:

kernel: possible SYN flooding on port 80. Sending cookies

下面引用其他博文的相关代码,如果sysctl_tcp_abort_on_overflow为0,直接丢弃什么都没做:

如果队列已满,跳到exit_overflow标签执行一些清理工作、更新/proc/net/netstat中的统计项ListenOverflows和ListenDrops,最后返回NULL。这会触发tcp_check_req函数跳到listen_overflow标签执行代码。

1
2
3
4
5
listen_overflow:
if (!sysctl_tcp_abort_on_overflow) {
inet_rsk(req)->acked = 1;
return NULL;
}

附录1:

测试随机丢弃SYN的相关资料,本人未验证,供参考

附录2:一个客户正尝试连接一个已经达到其最大backlog的socket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 0.000  127.0.0.1 -> 127.0.0.1  TCP 74 53302 > 9999 [SYN] Seq=0 Len=0
0.000 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
0.000 127.0.0.1 -> 127.0.0.1 TCP 66 53302 > 9999 [ACK] Seq=1 Ack=1 Len=0
0.000 127.0.0.1 -> 127.0.0.1 TCP 71 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
0.207 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
0.623 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
1.199 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
1.199 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 6#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
1.455 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
3.123 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
3.399 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
3.399 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 10#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
6.459 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
7.599 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
7.599 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 13#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
13.131 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
15.599 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
15.599 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 16#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
26.491 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
31.599 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
31.599 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 19#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
53.179 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
106.491 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
106.491 127.0.0.1 -> 127.0.0.1 TCP 54 9999 > 53302 [RST] Seq=1 Len=0

当Accept Queue满时,处在listen状态和syn_recv状态,server分别如何处理?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#server端

import time
import socket

if __name__ == "__main__":
s =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind(("0.0.0.0",11111))
s.listen(1)
time.sleep(10000)

#client端
import socket
if __name__ == "__main__":
l=[]
for i in xrange(100):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("10.9.198.26",11111))
l.append(s)
print i,s

结论:

  • 如果Accpet Queue溢出,就算syn queue没有溢出,处在LISTEN状态,新的SYN也有可能被丢弃,也有可能被收到回复SYNACK
  • 如果Accept Queue溢出,当接收SYN后并返回SYNACK,处在SYN-RECV状态再次收到ACK,ACK会直接丢弃

实验抓包图及分析:

如上图,
packet 1,client发送syn,此时server accept queue 队列满,syn 被server端丢弃
packet 2,client 1s后重传syn,重传后,server依旧丢弃
packet 3,client 1s后再次重传syn,重传后,server接收,进入SYN-RECV
packet 4,server回复syn+ack,server依旧为SYN-RECV状态
packet 5,client 收到syn+ack后,回复ACK,进入ESTAB状态 ,但是此包因为server backlog满,被丢弃
packet 6,server丢弃完client的ACK后,重传SYN+ACK
packet 7,约3s后,server继续重传syn+ack

另,测试机器backlog满导致listen drop 和 listen overflow ,见下图和下节分析

当修改sysctl -w net.ipv4.tcp_abort_on_overflow=1时,客户端直接收到RST,不会重传

连接被丢弃的计数

如果全连接队列accept queue满了后,会出现如下计数:

  • 进入半连接队列的SYN包将会被丢弃(三次握手第一个包)
  • 进入半连接队列的ACK包将会被丢弃(三次握手第三个包)
  • TcpExtListenOverflows 计数增加
  • TcpExtListenDrops 计数增加

sk_backlog_queue

在tcp_v4_rcv()中,如果accept queue被锁住,内核会把包加到sk_backlog_queue,此时如果sk_rcv_buf不足的话会入队失败,TCPBacklogDrop counter加1
对应系统参数:
net.ipv4.tcp_wmem
net.ipv4.tcp_rmem
如果nginx出现TCPBacklogDrop计数,需要调整上述两个参数后,restart nginx.
详见下节优化部分.

Nginx 调优

  • 增大backlog, listen 80 修改为listen 80 backlog=2048 (需要和somaxcon协助修改,取最小值)
  • net.ipv4.tcp_max_syn_backlog 稍微调大,并开启net.ipv4.tcp_syncookies=1 ; (cookies开启后逻辑syn_backlog无最大值)
  • 增大net.core.somaxconn 和 netdev_max_backlog,和应用程序(nginx listen backlog)配合修改,nginx默认为511;
  • /proc/sys/net/ipv4/tcp_synack_retries 次数可适当减小到3次
  • /proc/sys/net/ipv4/tcp_abort_on_overflow 设置为0,真出现backlog满时,不要直接发送RST重置连接,直接丢弃SYN或ACK,等待客户端重试;
  • so_recv_buf相关参数优化,可解决backlogDrop问题:
1
2
3
4
5
6
7
/proc/sys/net/ipv4/tcp_wmem的第二个值会被net.core.wmem_default覆盖,第三个值会被net.core.wmem_max覆盖,修改时请注意同步修改。
sysctl -w net.core.wmem_max=16777216
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_default=2621444
sysctl -w net.core.rmem_default=2621444
echo "40960 2621444 16777216" > /proc/sys/net/ipv4/tcp_wmem
echo "40960 2621444 16777216" > /proc/sys/net/ipv4/tcp_rmem

Nginx增加backlog后

优化so_recv_buf相关参数优化后

net.core.somaxconn 和 listen 80 backlog 设置多少为合理

backlog 过小:

  • 客户端第三次ACK被丢弃,以为ESTAB,发送数据
  • connection timeout

backlog过大:
如果请求量大,导致请求积压在backlog,等处理完请求

相关案例分析

todo

感谢

本文在本人工作经验上,结合互联网大牛总结的博客,以及同事的经验,总结此文。供参考

相关文章:
https://yq.aliyun.com/articles/79972
https://mp.weixin.qq.com/s/kP6EaR5aHBa0jDpy2A8UkA

如有问题请留言!



支付宝打赏 微信打赏

赞赏支持一下