- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章从一次线上问题说起,详解 TCP 半连接队列、全连接队列由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
本文转载自微信公众号「云巅论剑」,作者黄刚。转载本文请联系云巅论剑公众号.
某次大促值班 ing,对系统稳定性有着充分信心、心态稳如老狗的笔者突然收到上游反馈有万分几的概率请求我们 endpoint 会出现 Connection timeout 。此时系统侧的 apiserver 集群水位在 40%,离极限水位还有着很大的距离,当时通过紧急扩容 apiserver 集群后错误率降为了 0。事后进行了详细的问题排查,定位分析到问题根因出现在系统连接队列被打满导致,之前笔者对 TCP 半连接队列、全连接队列不太了解,只依稀记得 《TCP/IP 详解》中好像有好像提到过这两个名词.
目前网上相关资料都比较零散,并且有些是过时或错误的结论,笔者在调查问题时踩了很多坑。痛定思痛,笔者查阅了大量资料并做了众多实验进行验证,梳理了这篇 TCP 半连接队列、全连接详解,当你细心阅读完这篇文章后相信你可以对 TCP 半连接队列、全连接队列有更充分的认识.
本篇文章将结合理论知识、内核代码、操作实验为你呈现如下内容:
在 TCP 三次握手的过程中,Linux 内核会维护两个队列,分别是:
正常的 TCP 三次握手过程:
1、Client 端向 Server 端发送 SYN 发起握手,Client 端进入 SYN_SENT 状态 。
2、Server 端收到 Client 端的 SYN 请求后,Server 端进入 SYN_RECV 状态,此时内核会将连接存储到半连接队列(SYN Queue),并向 Client 端回复 SYN+ACK 。
3、Client 端收到 Server 端的 SYN+ACK 后,Client 端回复 ACK 并进入 ESTABLISHED 状态 。
4、Server 端收到 Client 端的 ACK 后,内核将连接从半连接队列(SYN Queue)中取出,添加到全连接队列(Accept Queue),Server 端进入 ESTABLISHED 状态 。
5、Server 端应用进程调用 accept 函数时,将连接从全连接队列(Accept Queue)中取出 。
半连接队列和全连接队列都有长度大小限制,超过限制时内核会将连接 Drop 丢弃或者返回 RST 包.
通过 ss 命令可以查看到全连接队列的信息 。
相关内核代码:
netstat 命令 。
通过 netstat -s 命令可以查看 TCP 半连接队列、全连接队列的溢出情况 。
上面输出的数值是累计值,分别表示有多少 TCP socket 链接因为全连接队列、半连接队列满了而被丢弃 。
在排查线上问题时,如果一段时间内相关数值一直在上升,则表明半连接队列、全连接队列有溢出情况 。
TCP 全连接队列的最大长度由 min(somaxconn, backlog) 控制,其中:
相关内核代码:
实验 。
服务端 server 代码 。
在测试环境查看 somaxconn 的值为 128 。
启动服务端,通过 ss -lnt | grep :8888 确认全连接队列大小 。
全连接队列最大长度为 128 。
现在更新 somaxconn 值为 1024,再重新启动服务端.
1、更新 /etc/sysctl.conf 文件,该文件为内核参数配置文件 。
a.新增一行 net.core.somaxconn=1024 。
2、执行 sysctl -p 使配置生效 。
3、检查 /proc/sys/net/core/somaxconn 文件,确认 somaxconn 为更新后的 1024 。
重新启动服务端, 通过 ss -lnt | grep :8888 确认全连接队列大小 。
可以看到,现在全链接队列最大长度为 1024,成功更新.
全连接队列溢出 。
下面来验证下全连接队列溢出会发生什么情况,可以通过让服务端应用只负责 Listen 对应端口而不执行 accept() TCP 连接,使 TCP 全连接队列溢出.
实验物料 。
服务端 server 代码 。
客户端 client 代码 。
为了方便实验,将 somaxconn 全连接队列最大长度更新为 5:
1、更新 /etc/sysctl.conf 文件,将 net.core.somaxconn 更新为 5 。
2、执行 sysctl -p 使配置生效 。
实验结果 。
客户端日志输出 。
客户端 socket 情况 。
服务端 socket 情况 。
抓包结果 。
对客户端、服务端抓包后,发现出现了三种情况,分别是:
全连接队列实验结果分析 。
上述实验结果出现了三种情况,我们分别对抓包内容进行分析 。
情况一:Client 成功与 Server 端建立 tcp socket 链接,发送数据成功 。
上图可以看到如下请求:
这种情况就是正常的请求,即全连接队列、半连接队列未满,client 成功与 server 建立了 tcp 链接,并成功发送数据.
情况二:Client 认为成功与 Server 端建立 tcp socket 连接,后续发送数据失败,持续 RETRY;Server 端认为 TCP 连接未建立,一直在发送SYN+ACK 。
上图可以看到如下请求:
为什么会出现上述情况?Server 端为什么一直在 RETRY 发送 SYN+ACK?Server 端不是已经收到了 Client 端的 ACK 确认了吗?
上述情况是由于 Server 端 socket 连接进入了半连接队列,在收到 Client 端 ACK 后,本应将 socket 连接存储到全连接队列,但是全连接队列已满,所以 Server 端 DROP 了该 ACK 请求.
之所以 Server 端一直在 RETRY 发送 SYN+ACK,是因为 DROP 了 client 端的 ACK 请求,所以 socket 连接仍旧在半连接队列中,等待 Client 端回复 ACK.
tcp_abort_on_overflow 参数控制 。
全连接队列满DROP 请求是默认行为,可以通过设置 /proc/sys/net/ipv4/tcp_abort_on_overflow 使 Server 端在全连接队列满时,向 Client 端发送 RST 报文.
tcp_abort_on_overflow 有两种可选值:
为什么实验结果中当前全连接队列大小 > 全连接队列最大长度配置?
上述结果中可以看到 Listen 状态的 socket 链接:
为什么全连接队列大小 > 全连接队列最大长度配置呢?
经过多次实验发现,能够进入全连接队列的 Socket 最大数量始终比配置的全连接队列最大长度 + 1.
结合其他文章以及内核代码,发现内核在判断全连接队列是否满的情况下,使用的是 > 而非 >= (具体是为什么没有找到相关资源 : ) ).
相关内核代码:
情况三:Client 向 Server 发送 SYN 未得到相应,一直在 RETRY 。
图片上图可以看到如下请求:
(这种情况涉及到半连接队列,这里先给上述情况发生的原因结论,具体内容将在下文半连接队列中展开。) 。
发生上述情况的原因由以下两方面导致:
1、开启了 /proc/sys/net/ipv4/tcp_syncookies 功能 。
2、全连接队列满了 。
翻阅了很多博文,查找关于半连接队列最大长度控制的相关内容,大多含糊其辞或不准确,经过不懈努力,最终找到了比较确切的内容(相关博文链接在附录中).
很多博文中说半连接队列最大长度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 参数指定,实际上只有在 linux 内核版本小于 2.6.20 时,半连接队列才等于 backlog 的大小.
这块的源码比较复杂,这里给一下大体的计算方式,详细的内容可以参考附录中的相关博文。半连接队列长度的计算过程:
可以看到,半连接队列的长度由三个参数指定:
我们假设 listen 传入的 backlog = 128 (Golang 中调用 listen 时传递的 backlog 参数使用的是 /proc/sys/net/core/somaxconn),其他配置采用默认值,来计算下半连接队列的最大长度 。
可以得到半队列大小是 256.
当 Client 端向 Server 端发送 SYN 报文后,Server 端会将该 socket 连接存储到半连接队列(SYN Queue),如果 Server 端判断半连接队列满了则会将连接 Drop 丢弃.
那么 Server 端是如何判断半连接队列是否满的呢?除了上面一小节提到的半连接队列最大长度控制外,还和 /proc/sys/net/ipv4/tcp_syncookies 参数有关。(tcp_syncookies 的作用是为了防止 SYN Flood 攻击的,下文会给出相关链接介绍) 。
流程图 。
判断是否 Drop SYN 请求的流程图:
上图是整理了多份资料后,整理出来的判断是否 Drop SYN 请求的流程图.
注意:第一个判断条件 「当前半连接队列是否已超过半连接队列最大长度」在不同内核版本中的判断不一样,Linux4.19.91 内核判断的是当前半连接队列长度是否 >= 全连接队列最大长度.
相关内核代码:
我们假设如下参数,来计算下当 Client 端只发送 SYN 包,理论上 Server 端何时会 Drop SYN 请求:
当 /proc/sys/net/ipv4/tcp_syncookies 值为 0 时 。
当 /proc/sys/net/ipv4/tcp_syncookies 值为 1 时 。
1.计算出的半连接队列最大长度为 256 。
2.由于开启了 tcp_syncookies 。
PS:/proc/sys/net/ipv4/tcp_syncookies 的取值还可以为 2,笔者没有详细实验.
回顾全连接队列实验结果 。
在上文全连接队列实验中,有一类实验结果是:client 向 Server 发送 SYN 未得到响应,一直在 RETRY.
发生上述情况的原因由以下两方面导致:
1. 开启了 /proc/sys/net/ipv4/tcp_syncookies 功能 。
2. 全连接队列满了 。
半连接队列溢出实验 。
上文我们已经知道如何计算理论上半连接队列何时会溢出,下面我们来具体实验下 。
(Golang 调用 listen 时传入的 backlog 值为 somaxconn) 。
理论上:
将相关参数的配置更新 。
启动服务端 Server 监听 8888 端口(代码参考全连接队列实验物料) 。
客户端 Client 发起 SYN Flood 攻击:
查看服务端 Server 8888端口处于 SYN_RECV 状态的 socket 最大个数:
实验结果符合预期,当半连接队列长度增长至 96 后,后续 SYN 请求就会触发 Drop.
理论上:
将相关参数的配置更新 。
启动服务端 Server 监听 8888 端口(代码参考全连接队列实验物料) 。
客户端 Client 发起 SYN Flood 攻击:
查看服务端 Server 8888端口处于 SYN_RECV 状态的 socket 最大个数:
实验结果符合预期,当半连接队列长度增长至 128 后,后续 SYN 请求就会触发 Drop 。
理论上:
将相关参数的配置更新 。
启动服务端 Server 监听 8888 端口(代码参考全连接队列实验物料) 。
客户端 Client 发起 SYN Flood 攻击:
查看服务端 Server 8888端口处于 SYN_RECV 状态的 socket 最大个数:
实验发现即使syncookies=1,当半连接队列长度 > 全连接队列最大长度时,就会触发 DROP SYN 请求!!!(TODO:有时间阅读下相关内核源码,再分析下) 。
继续做实验,将 somaxconn 更新为 5 。
发起 SYN Flood 攻击后,查看服务端 Server 8888端口处于 SYN_RECV 状态的 socket 最大个数:
确实 即使 syncookies=1,当半连接队列长度 > 全连接最大长度时,就会触发 DROP SYN 请求.
理论上:
将相关参数的配置更新 。
启动服务端 Server 监听 8888 端口(代码参考全连接队列实验物料).
客户端 Client 发起 SYN Flood 攻击
查看服务端 Server 8888端口处于 SYN_RECV 状态的 socket 最大个数:
实验结果符合预期,当半连接队列长度增长至 256 后,后续 SYN 请求就会触发 Drop.
再回顾值班时遇到的 Connection timeout 问题,当时相关系统参数配置为:
所以出现 Connection timeout 有两种可能情况:
1、半连接队列未满,全连接队列满,Client 端向 Server 端发起 SYN 被 DROP (参考全连接队列实验结果情况三分析、半连接队列溢出实验情况三) 。
2、全连接队列未满,半连接队列大小超过全链接队列最大长度(参考半连接队列溢出实验情况3、半连接队列溢出实验情况四) 。
问题的最快修复方式是将 net.core.somaxconn 调大,以及 net.ipv4.tcp_abort_on_overflow 设置为 1,net.ipv4.tcp_abort_on_overflow 设置为 1 是为了让 client fail fast.
半连接队列溢出、全连接队列溢出这类问题很容易被忽略,同时这类问题又很致命。当半连接队列、全连接队列溢出时 Server 端,从监控上来看系统 cpu 水位、内存水位、网络连接数等一切正常,然而却会持续影响 Client 端业务请求。对于高负载上游使用短连接的情况,出现这类问题的可能性更大.
本文详细梳理了 TCP 半连接队列、全连接队列的理论知识,同时结合 Linux 相关内核代码以及详细的动手实验,讲解了 TCP 半连接队列、全连接队列的相关原理、溢出判断、问题分析等内容,希望大家在阅读后可以对 TCP 半连接队列、全连接队列有更充分的认识.
PS:可以去线上检查下服务器的相关参数哟~ 。
附录 。
这里罗列下相关参考博文资料:
Linux 源码 。
Linux 诡异的半连接队列长度 。
TCP 半连接队列和全连接队列满了会发生什么 。
一次 HTTP connect-timeout 排查 。
Connection Reset 排查 。
深入浅出 TCP 中的 SYN-Cookies 。
原文链接:https://mp.weixin.qq.com/s/YpSlU1yaowTs-pF6R43hMw 。
最后此篇关于从一次线上问题说起,详解 TCP 半连接队列、全连接队列的文章就讲到这里了,如果你想了解更多关于从一次线上问题说起,详解 TCP 半连接队列、全连接队列的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我知道这个问题可能已经被问过,但我检查了所有这些,我认为我的情况有所不同(请友善)。所以我有两个数据集,第一个是测试数据集,第二个是我保存在数据框中的预测(预测值,这就是没有数据列的原因)。我想合并两
在 .loc 方法的帮助下,我根据同一数据框中另一列中的值来识别 Panda 数据框中某一列中的值。 下面给出了代码片段供您引用: var1 = output_df['Player'].loc[out
当我在 Windows 中使用 WinSCP 通过 Ubuntu 连接到 VMware 时,它提示: The server rejected SFTP connection, but it lis
我正在开发一个使用 xml web 服务的 android 应用程序。在 wi-fi 网络中连接时工作正常,但在 3G 网络中连接时失败(未找到 http 404)。 这不仅仅发生在设备中。为了进行测
我有一个XIB包含我的控件的文件,加载到 Interface Builder(Snow Leopard 上的 Xcode 4.0.2)中。 文件的所有者被设置为 someClassController
我在本地计算机上管理 MySQL 数据库,并通过运行以下程序通过 C 连接到它: #include #include #include int main(int argc, char** arg
我不知道为什么每次有人访问我网站上的页面时,都会打开一个与数据库的新连接。最终我到达了大约 300 并收到错误并且页面不再加载。我认为它应该工作的方式是,我将 maxIdle 设置为 30,这意味着
希望清理 NMEA GPS 中的 .txt 文件。我当前的代码如下。 deletes = ['$GPGGA', '$GPGSA', '$GPGSV', '$PSRF156', ] searchquer
我有一个 URL、一个用户名和一个密码。我想在 C# .Net WinForms 中建立 VPN 连接。 你能告诉我从哪里开始吗?任何第三方 API? 代码示例将受到高度赞赏... 最佳答案 您可以像
有没有更好的方法将字符串 vector 转换为字符 vector ,字符串之间的终止符为零。 因此,如果我有一个包含以下字符串的 vector "test","my","string",那么我想接收一
我正在编写一个库,它不断检查 android 设备的连接,并在设备连接、断开连接或互联网连接变慢时给出回调。 https://github.com/muddassir235/connection_ch
我的操作系统:Centos 7 + CLOUDLINUX 7.7当我尝试从服务器登录Mysql时 [root@server3 ~]# Mysql -u root -h localhost -P 330
我收到错误:Puma 发现此错误:无法打开到本地主机的 TCP 连接:9200(连接被拒绝 - 连接(2)用于“本地主机”端口 9200)(Faraday::ConnectionFailed)在我的
请给我一些解决以下错误的方法。 这是一个聊天应用....代码和错误如下:: conversations_controller.rb def create if Conversation.bet
我想将两个单元格中的数据连接到一个单元格中。我还想只组合那些具有相同 ID 的单元格。 任务 ID 名称 4355.2 参与者 4355.2 领袖 4462.1 在线 4462.1 快速 4597.1
我经常需要连接 TSQL 中的字段... 使用“+”运算符时 TSQL 强制您处理的两个问题是 Data Type Precedence和 NULL 值。 使用数据类型优先级,问题是转换错误。 1)
有没有在 iPad 或 iPhone 应用程序中使用 Facebook 连接。 这个想法是登录这个应用程序,然后能够看到我的哪些 facebook 用户也在使用该应用程序及其功能。 最佳答案 是的。
我在连接或打印字符串时遇到了一个奇怪的问题。我有一个 char * ,可以将其设置为字符串文字的几个值之一。 char *myStrLiteral = NULL; ... if(blah) myS
对于以下数据 - let $x := "Yahooooo !!!! Select one number - " let $y := 1 2 3 4 5 6 7 我想得到
我正在看 UDEMY for perl 的培训视频,但是视频不清晰,看起来有错误。 培训展示了如何使用以下示例连接 2 个字符串: #!usr/bin/perl print $str = "Hi";
我是一名优秀的程序员,十分优秀!