问题
最近发现客户端调用RT比较高,并且偶尔Connection reset出现,而查看服务运行情况,资源占用cpu、memory和线程都正常。通过分析是服务的一个api接口处理过慢,慢接口导致tcp全链接队列溢出
全链接队列和半链接队列
- tcp三次握手时,linux内核会维护两个队列:
全链接队列,也称为syn队列
半链接队列,也称为accept队列
三次握手过程
1、 客户端发送SYN包,并进入SYN_SENT状态
2、服务端接收到数据包将相关信息放入半连接队列(SYN 队列),并返回SYN+ACK包给客户端。
3、服务端接收客户端ACK数据包,这时如果全连接队列(accept 队列)没满,就会从半连接队列里面将数据取出来放入全连接队列,等待应用使用,当队列已满就会跟据tcp_abort_on_overflow配置执行策略。
查看全链接队列
执行命令:ss
ss 命令获取数据又分为LISTEN 状态,和非LISTEN 状态。
-
ss -tln
其中l显示正在listener的socket n指不解析服务名称 t指tcp链接
Recv-Q 完成三次握手并等待服务端 accept() 的 TCP 全连接总数,(全链接队列里的tcp连接个数)
Send-Q 全连接队列大小 -
ss -tn
Recv-Q 已收到但未被应用进程读取的字节数
Send-Q 已发送但未收到确认的字节数 -
查看全链接队列溢出次数,执行命令: netstat -s | egrep “listen|LISTEN”
解决办法
-
TCP三次握手的最后一步,当全连接队列已满就会根据tcp_abort_on_overflow策略进行处理。Linux 可通过 /proc/sys/net/ipv4/tcp_abort_on_overflow 进行配置。
-
当tcp_abort_on_overflow=0,服务accept 队列满了,客户端发来ack,服务端直接丢弃该ACK,此时服务端处于【syn_rcvd】的状态,客户端处于【established】的状态。在该状态下会有一个定时器重传服务端 SYN/ACK 给客户端(不超过 /proc/sys/net/ipv4/tcp_synack_retries 指定的次数,Linux下默认5)。超过后,服务器不在重传,后续也不会有任何动作。如果此时客户端发送数据过来,服务端会返回RST。(这也就是我们的异常原因了)
-
当tcp_abort_on_overflow=1,服务端accept队列满了,客户端发来ack,服务端直接返回RST通知client,表示废掉这个握手过程和这个连接,client会报connection reset by peer。
-
全连接队列大小取决于backlog 和somaxconn 的最小值,也就是 min(backlog,somaxconn)
-
somaxconn 是Linux内核参数,默认128,可通过/proc/sys/net/core/somaxconn进行配置
-
backlog是 listen(int sockfd,int backlog)函数中的参数backlog,Tomcat 默认100,Nginx 默认511.
backlog配置
- Nginx 配置 server{ listen 8080 default_server backlog=512}
- Redis 配置redis.conf文件 tcp-backlog 511参数