首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何正确查看线上半/全连接队列溢出情况?

如何正确查看线上半/全连接队列溢出情况?

作者头像
开发内功修炼
发布2022-03-24 17:58:19
发布2022-03-24 17:58:19
2K00
代码可运行
举报
文章被收录于专栏:开发内功修炼开发内功修炼
运行总次数:0
代码可运行

大家好,我是飞哥!

《深入解析常见三次握手异常》 这一文中,我们讨论到如果发生连接队列溢出而丢包的话,会导致连接耗时会上涨很多。那如何判断一台服务器当前是否有半/全连接队列溢出丢包发生呢?

我在我早期的一篇文章里提到过,可以通过 netstat 来查看。

代码语言:javascript
代码运行次数:0
运行
复制
# netstat -s 
    8 SYNs to LISTEN sockets ignored
    160 times the listen queue of a socket overflowed
    ...
  • SYNs to LISTEN ockets ignored 前面的数字是半连接队列的溢出,
  • times the listen queue of a socket overflowed 前面的数字是全连接队列的溢出。

如果这两个数字在动态增长,那就说明当前有溢出发生了。

但可惜这个说法存在一些问题。其中对于全连接队列溢出描述 ok,但半连接队列的描述很不正确!所以我今天专门发篇文章纠正一下,来从源码角度来分析一下为啥这样说。

一、全连接队列溢出判断

全连接队列溢出判断比较简单,所以先说这个。

1. 全连接溢出丢包

全连接队列溢出都会记录到 ListenOverflows 这个 MIB(Management Information Base,管理信息库)上的,对应 SNMP 统计信息中的 ListenDrops 这一项。我们来展开看一下相关的源码。

服务器在响应客户端的 SYN 握手包的时候,有可能会在 tcp_v4_conn_request 这里发生全连接队列溢出而丢包。

代码语言:javascript
代码运行次数:0
运行
复制
//file: net/ipv4/tcp_ipv4.c
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
 //看看半连接队列是否满了
 ...

 //看看全连接队列是否满了
 if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
  NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
  goto drop;
 }
 ...
drop:
 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
 return 0;
}

从上述代码中可以看到,全连接队列满了以后调用 NET_INC_STATS_BH 增加了 LINUX_MIB_LISTENOVERFLOWS 和 LINUX_MIB_LISTENDROPS 这两个 MIB。

服务器在响应第三次握手的时候,会再次判断全连接队列是否溢出。如果溢出,一样会增加这两个 MIB。源码如下:

代码语言:javascript
代码运行次数:0
运行
复制
//file: net/ipv4/tcp_ipv4.c
struct sock *tcp_v4_syn_recv_sock(...)
{
 if (sk_acceptq_is_full(sk))
  goto exit_overflow;
 ...
exit_overflow:
 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS); 
exit:
 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
 return NULL;
} 

在 proc.c 中, LINUX_MIB_LISTENOVERFLOWS 和 LINUX_MIB_LISTENDROPS 都被整合进了 SNMP 统计信息。

代码语言:javascript
代码运行次数:0
运行
复制
//file: net/ipv4/proc.c
static const struct snmp_mib snmp4_net_list[] = {
 SNMP_MIB_ITEM("ListenDrops", LINUX_MIB_LISTENDROPS),
 SNMP_MIB_ITEM("ListenOverflows", LINUX_MIB_LISTENOVERFLOWS),
 ......
}

2.netstat 工具源码

我们在执行 netstat -s 的时候,该工具会读取 SNMP 统计信息并展现出来。netstat 命令属于 net-tool 工具集,所以得找 net-tool 的源码。我用 SYNs to LISTEN sockets dropped 这种关键词去搜到了:

代码语言:javascript
代码运行次数:0
运行
复制
//file: https://github.com/giftnuss/net-tools/blob/master/statistics.c
struct entry Tcpexttab[] =
{
 { "ListenDrops", N_("%u SYNs to LISTEN sockets dropped"), opt_number },
 { "ListenOverflows", N_("%u times the listen queue of a socket overflowed"),
 ......
}

以上这些就是执行 netstat -s 时会执行到的源码。它从 SNMP 统计信息中获取到 ListenDrops 和 ListenOverflows 这两项显示了出来,分别对应 LINUX_MIB_LISTENDROPS 和 LINUX_MIB_LISTENOVERFLOWS 这两个 MIB。

代码语言:javascript
代码运行次数:0
运行
复制
# watch 'netstat -s | grep overflowed'
 198 times the listen queue of a socket overflowed

所以,每当发生全连接队列满导致的丢包的时候,可以通过上述命令的结果里体现出来。而且幸运的是,ListenOverflows 这个 SNMP 统计项只有在全连接队列满的时候才会增加,内核源码其它地方没有用到。

所以,通过 netstat -s 输出中的 xx times the listen queue 中的查看到数字如果有变化,那么一定是你的服务器上发生了全连接队列溢出了!!

二、半连接队列溢出判断

再来看半连接队列,溢出时是更新的是 LINUX_MIB_LISTENDROPS 这个 MIB,对应到 SNMP 就是 ListenDrops 这个统计项。

代码语言:javascript
代码运行次数:0
运行
复制
//file: net/ipv4/tcp_ipv4.c
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
 //看看半连接队列是否满了
 if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
  want_cookie = tcp_syn_flood_action(sk, skb, "TCP");
  if (!want_cookie)
   goto drop;
 }

 //看看全连接队列是否满了
 if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
  NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
  goto drop;
 }
 ...
drop:
 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
 return 0; 
}

上述源码中可见,半连接队列满的时候 goto drop,然后增加了 LINUX_MIB_LISTENDROPS 这个 MIB。通过上一节 netstat -s 的源码我们看到也会展示它出来(对应 SNMP 中的 ListenDrops 这个统计项)。

但是问题在于,不仅仅只是在半连接队列发生溢出的时候会增加该值。所以根据 netstat -s 看半连接队列是否溢出是不靠谱的!

上面看到,即使半连接队列没问题,全连接队列满了该值也会增加。另外就是当在 listen 状态握手发生错误的时候,进入 tcp_v4_err 函数时也会增加该值。

对于如何查看半连接队列溢出丢包这个问题,我的建议是不要纠结咋看是否丢包了。直接看服务器上的 tcp_syncookies 是不是 1 就行。

如果该值是 1 ,那么下面代码中 want_cookie 就返回是真,是根本不会发生半连接溢出丢包的。

代码语言:javascript
代码运行次数:0
运行
复制
//file: net/ipv4/tcp_ipv4.c
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
 //看看半连接队列是否满了
 if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
  want_cookie = tcp_syn_flood_action(sk, skb, "TCP");
  if (!want_cookie)
   goto drop;
 }

如果 tcp_syncookies 不是 1,则建议改成 1 就完事了。

如果因为各种原因就是不想打开 tcp_syncookies。就想死磕看下是否有因为半连接队列满而导致的 SYN 丢弃,除了 netstat -s 的结果,我建议同时查看下当前 listen 的端口上的 SYN_RECV 的数量。

代码语言:javascript
代码运行次数:0
运行
复制
# netstat -antp | grep SYN_RECV 
256

《为什么服务端程序都需要先 listen 一下?》中我们讨论了半连接队列的实际长度怎么计算。如果 SYN_RECV 状态的连接数量达到你算出来的队列长度了,那么可以确定是有半连接队列溢出了。如果想加大半连接队列的长度,方法我们在上面文章里也一并讲过了。

三、总结

最后,总结一下。

对于全连接队列来说,使用 netstat -s (最好再配合 watch 命令动态观察)就可以判断是否有丢包发生。如果看到 “xx times the listen queue of a socket overflowed” 中的数值在增长,那么就确定是全连接队列满了。

代码语言:javascript
代码运行次数:0
运行
复制
# watch 'netstat -s | grep overflowed'
 198 times the listen queue of a socket overflowed

对于半连接队列来说,只要保证 tcp_syncookies 这个内核参数是 1 就能保证不会有因为半连接队列满而发生的丢包。

如果确实较真就像看一看,netstat -s | grep "SYNs" 这个是没有办法说明问题的。还需要你自己计算一下半连接队列的长度,再看下当前 SYN_RECV 状态的连接的数量。

代码语言:javascript
代码运行次数:0
运行
复制
# watch 'netstat -s | grep "SYNs"'
 258209 SYNs to LISTEN sockets dropped 
# netstat -antp | grep SYN_RECV | wc -l
5 

至于如何加大半连接队列长度,

结尾再感叹一句,很多问题真的是只有通过源码才能找到最准确的结论啊!!

最后,求再看,求转发!

Github: https://github.com/yanfeizhang/coder-kung-fu

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发内功修炼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、全连接队列溢出判断
    • 1. 全连接溢出丢包
    • 2.netstat 工具源码
  • 二、半连接队列溢出判断
  • 三、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档