我们先对文章内容的总结:
tcp_max_syn_backlog
、net.core.somaxconn
和listen(fd, backlog)
的backlog
三者最小值决定。net.core.somaxconn
和listen(fd, backlog)
的backlog
两者最小值决定。net.core.somaxconn
和tcp_max_syn_backlog
参数来增加队列大小。somaxconn
参数来增加队列大小,并根据tcp_abort_on_overflow
参数决定是丢弃ACK包还是发送RST包给客户端。tcp_max_syn_backlog
:定义系统可以同时为还未完成三次握手的连接保留多少个半连接队列位置。net.core.somaxconn
:指定系统中所有套接字监听队列的最大长度。tcp_abort_on_overflow
:决定全连接队列溢出时的行为(丢弃ACK或发送RST)。netstat -ant
或 ss -ant
:查看本地的TCP连接状态,检查SYN_SENT的数量是否异常。sysctl net.core.somaxconn
:查看和设置somaxconn
的值。sysctl net.ipv4.tcp_max_syn_backlog
:查看TCP半连接队列的最大长度。cat /proc/sys/net/ipv4/tcp_abort_on_overflow
:查看tcp_abort_on_overflow
的当前值。netstat -s | grep "overflowed"
:查看全连接队列溢出的次数。wrk是一个基于C语言编写的HTTP性能测试工具,由GitHub用户wg/wrk开发。它能够通过生成大量的HTTP请求,对服务器进行压力测试,并实时输出测试结果,包括请求速率、传输速率、连接数等关键性能指标。wrk的设计初衷是为了提供一个简单易用的性能测试工具,同时保证测试结果的准确性和可靠性。
wrk的使用非常简单,基本的命令格式如下:
wrk [options] http://host:port/path
其中,[options]是可选的参数,http://host:port/path是待测试的URL。下面是一些常用的选项:
-c, --connections
:设置并发连接数。-t, --threads
:设置线程数。-d, --duration
:设置测试持续时间(秒)。-D, --header
:添加自定义请求头。-H, --default-header
:设置默认请求头。-s, --script
:指定Lua脚本文件,用于自定义请求行为。例如,要对一个Web服务器进行压力测试,可以使用以下命令:
wrk -c 100 -t 10 -d 60 http://example.com/
这个命令将会模拟100个并发连接,使用10个线程,持续测试60秒。
TCP 全连接队列的最大值取决于 somaxconn 和 backlog 之间的最小值,也就是 min(somaxconn, backlog),其中:
相关的内核代码:
// https://github.com/torvalds/linux/blob/master/net/socket.c /* * Perform a listen. Basically, we allow the protocol to do anything * necessary for a listen, and if that works, we mark the socket as * ready for listening. */ int __sys_listen(int fd, int backlog) { struct socket *sock; int err, fput_needed; int somaxconn; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) { somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn; // /proc/sys/net/core/somaxconn if ((unsigned int)backlog > somaxconn) backlog = somaxconn; // TCP 全连接队列最大长度 min(somaxconn, backlog) err = security_socket_listen(sock, backlog); if (!err) err = sock->ops->listen(sock, backlog); fput_light(sock->file, fput_needed); } return err; }
查看全连接队列溢出的命令
ss
# -n 不解析服务名称 # -t 只显示 tcp sockets # -l 显示正在监听(LISTEN)的 sockets ss -lnt State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 511 0.0.0.0:80 0.0.0.0:* LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 128 127.0.0.1:631 0.0.0.0:* LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* LISTEN 0 511 [::]:80 [::]:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 128 [::1]:631 [::]:*
我们可以从源码看到 ss
命令获取的 Recv-Q/Send-Q
在「LISTEN 状态」和「非 LISTEN 状态」所表达的含义是不同的。
// https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_diag.c static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r, void *_info) { struct tcp_info *info = _info; if (inet_sk_state_load(sk) == TCP_LISTEN) { // socket 状态是 LISTEN 时 r->idiag_rqueue = READ_ONCE(sk->sk_ack_backlog); // 当前全连接队列大小 r->idiag_wqueue = READ_ONCE(sk->sk_max_ack_backlog); // 全连接队列最大长度 } else if (sk->sk_type == SOCK_STREAM) { // socket 状态不是 LISTEN 时 const struct tcp_sock *tp = tcp_sk(sk); r->idiag_rqueue = max_t(int, READ_ONCE(tp->rcv_nxt) - READ_ONCE(tp->copied_seq), 0); // 已收到但未被应用程序读取的字节数 r->idiag_wqueue = READ_ONCE(tp->write_seq) - tp->snd_una; // 已发送但未收到确认的字节数 } if (info) tcp_get_info(sk, info); }
# -n 不解析服务名称 # -t 只显示 tcp sockets # -l 显示正在监听(LISTEN)的 sockets
对于非 LISTEN 状态的 socket
# -n 不解析服务名称
# -t 只显示 tcp sockets
为了实验效果明显,我们修改系统的长连接队列设置
把somaxconn 设置为8
1、更新 /etc/sysctl.conf 文件,该文件为内核参数配置文件
a.新增一行 net.core.somaxconn=8
2、执行 sysctl -p 使配置生效
sudo sysctl -p
net.core.somaxconn = 8
3、检查 /proc/sys/net/core/somaxconn 文件,确认 somaxconn 为更新后的 8
cat /proc/sys/net/core/somaxconn
8
重新启动服务端, 通过 ss -lnt | grep :8888 确认全连接队列大小
ss -lnt | grep 8080
LISTEN 0 8 0.0.0.0:8080 0.0.0.0:*
可以看到,现在全链接队列最大长度为 64,成功更新。
部署nginx 服务
apt install nginx
配置nginx 监听
vim /etc/nginx/conf.d/bingo.conf
server {
listen 8080 default;
server_name localhost;
location / {
return 200 "bingo";
}
}
修改work数
vim /etc/nginx/nginx.conf
worker_processes 1;
relead nginx
nginx -s reload
进行持续的压测
wrk -t 6 -c 30000 -d 60s http://127.0.0.1:8080/
使用ss 命令查看当前TCP全连接队列的情况:
可以看到当前的TCP全连接对接到了9大于最大TCP全连接队列,一旦超过了系统设置的TCP最大全连接队列,服务端就会丢掉后续进来的请求,我们可以使用netstat -s 进行统计查看
可以按到514458times 表示全连接队列溢出的次数,注意这个是累计值。
可以隔几秒钟执行下,如果这个数字一直在增加的话肯定全连接队列偶尔满了。
通过抓包分析
可以看到很多SYN + ACK的报文,这个是因为全连接队列满了导致Client 认为成功与 Server 端建立 tcp socket 连接,后续发送数据失败,持续 RETRY;Server 端认为 TCP 连接未建立,一直在发送SYN+ACK.
可以看到请求流程如下:
Server 端 socket 连接进入了半连接队列,在收到 Client 端 ACK 后,本应将 socket 连接存储到全连接队列,但是全连接队列已满,所以 Server 端 DROP 了该 ACK 请求。
Server 端一直在 RETRY 发送 SYN+ACK,是因为 DROP 了 client 端的 ACK 请求,所以 socket 连接仍旧在半连接队列中,等待 Client 端回复 ACK。
全连接队列满DROP 请求是默认行为,可以通过设置 /proc/sys/net/ipv4/tcp_abort_on_overflow 使 Server 端在全连接队列满时,向 Client 端发送 RST 报文。
cat /proc/sys/net/ipv4/tcp_abort_on_overflow
0
tcp_abort_on_overflow 有两种可选值:
把 tcp_abort_on_overflow 设置为 1
root@adming-virtual-machine:/mnt/hgfs# echo "1" > /proc/sys/net/ipv4/tcp_abort_on_overflowroot@adming-virtual-machine:/mnt/hgfs# cat /proc/sys/net/ipv4/tcp_abort_on_overflow1
发起请求
wrk -t 6 -c 30000 -d 60s http://127.0.0.1:8080/
可以看到全连接队列已经满了
通过抓包可以看到很多reset的报文
默认设置:通常推荐将tcp_abort_on_overflow
设置为0,这有助于在面对突发流量时维持连接的稳定性。
流量管理:当TCP全连接队列因流量突增而溢出时,如果服务器丢弃了客户端的ACK包,客户端会认为连接未建立成功,从而触发重传机制。设置tcp_abort_on_overflow
为0允许系统在队列有空间时继续处理这些连接请求,而不是立即终止它们。
连接状态:即使在服务器端的全连接队列溢出的情况下,如果客户端的连接状态已经是ESTABLISHED,客户端进程仍然会尝试在已建立的连接上发送请求。由于服务器没有回复ACK,客户端会不断重发请求。
队列管理:如果服务器进程只是暂时繁忙导致accept
队列满,那么一旦TCP全连接队列有空闲,新的请求报文(包含ACK)可以触发服务器端成功建立连接。
设置场景:仅在确定TCP全连接队列会长期处于溢出状态时,才应将tcp_abort_on_overflow
设置为1,这样可以快速通知客户端连接无法建立,避免资源浪费。
权衡考虑:设置为1可以迅速释放资源,但可能会牺牲一些连接成功率。因此,除非有明确的性能问题,否则保持默认的0设置通常是更优的选择。
增大TCP全连接队列:在系统确认是全连接对接溢出,而且未定位到根因我们可以根据系统处理能力把全连接队列调大,来恢复或缓解线上故障会客户的影响。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。