一次对server服务大量积压异常TCP ESTABLISHED链接的排查笔记

背景

我们都知道,基于Kubernetes的微服务,大行其道,传统部署模式一直都在跟着变化,但其实,在原有业务向服务化方向过度过程中,有些场景可能会变得复杂。

比如说:将Kubernetes的模式应用到开发环节上,这个环节需要频繁的变更代码,微服务的方式,可能就需要不断的:

改代码->构建镜像->镜像推送->部署->拉去镜像->生成容器

尤其是PHP的业务,不需要构建二进制,仅需要发布代码,因此,如果按照上面的部署方式,就需要频繁改代码,走构建镜像这个流程,最后再做发布,这在开发环节就显得过于麻烦了,换而言之,有没有办法,能让开发直接将代码上传到容器中呢?

其实是有的,就是设计一个FTP中间件代理,让用户本地改完代码,通过FTP客户端(很多IDE是支持FTP的)直接上传到容器内部,甚至于用户保存一下代码就上传到容器内。

因此,这就引出了今天的主角,是我基于FTP协议+gRPC协议自研的FTP代理工具。

这个工具上线后,服务全公司所有研发,经过一段时间运行和修补,相对稳定,也做了一些关于内存方面的优化,直到又一次,在维护这个FTP代理的时候,发现一个奇怪的问题:

FTP代理进程,监听的是 192.168.88.32 的 21 端口,所以,这个端口对应了多少连接,就表示有多少个客户端存在,通过:

netstat -apn |grep "192.168.88.32:21"

发现,有将近1000个链接,且都是 ESTABLISHED,ESTABLISHED 状态表示一个连接的状态是“已连接”,但我们研发团队,并没有那么多人,直觉上看,事出反常必有妖。

初步分析可能性

感觉可能有一种情况,就是每个人开了多个FTP客户端,实际场景下,研发同学组可能会使用3种类型的FTP客户端

PHPStorm:这个客户端(SFTP插件)自己会维护一个FTP长连接。
Sublime + VsCode,这2个客户端不会维护链接,数据交互完成(比如传输任务),就主动发送 QUIT 指令到FTP代理端,然后所有链接关闭。很干净。

另外,使用PHPStorm的话,也存在开多个IDE创建,就使用多个FTP客户端连接的情况。
为了继续排查,我把所有对 192.168.88.32:21 的链接,做了分组统计,看看哪个IP的连接数最多

# 注:61604 是 ftp代理的进程ID
netstat -apn|grep "61604/server"|grep '192.168.88.32:21'|awk -F ':' '{print$2}'|awk '{print$2}'|sort|uniq -c |sort

上面的统计,是看哪个IP,对 192.168.88.32:21 连接数最多(18个)。

统计发现,很多IP,都存在多个链接的情况,难道每个人都用了多个IDE且可能还多IDE窗口使用吗?于是,挑了一个最多的,找到公司中使用这个IP的人,沟通发现,他确实使用了IDE多窗口,但是远远没有使用18个客户端那么多,仅仅PHPStorm开了3个窗口而已。

初步排查结论:应该是FTP代理所在服务器的问题,和用户开多个客户端没有关系。

进一步排查

这次排查,是怀疑,这将近1000个的 ESTABLISHED 客户端链接中,有大量假的 ESTABLISHED 链接存在,之前的统计发现,实际上,对 192.168.88.32:21 的客户端链接进行筛选,得到的IP,一共才200个客户端IP而已,平均下来,每个人都有5个FTP客户端链接FTP代理,想象觉得不太可能。那么,如何排查 ESTABLISHED 假链接呢?

在 TCP 四次挥手过程中,首先需要有一端,发起 FIN 包,接收方接受到 FIN 包之后,便开启四次挥手的过程,这也是连接断开的过程。

从之前的排查看,有人的IP,发起了多达18个FTP连接,那么,要排查是不是在 FTP 代理服务器上,存在假的 ESTABLISHED 连接的话,就首先需要去 开发同学的机器上看,客户端连接的端口,是不是仍在使用。比如:

tcp    ESTAB      0      0      192.168.88.32:21                 192.168.67.38:58038

这个表明,有一个研发的同学 IP是 192.168.67.38,使用了端口 58038,连接 192.168.88.32 上的 FTP 代理服务的 21 端口。所以,先要去看,到底研发同学的电脑上,这个端口存在不存在。

后来经过与研发同学沟通确认,研发电脑上并没有 58038 端口使用,这说明,对FTP代理服务的的客户端链接中显示的端口,也就是实际用户的客户端端口,存在大量不存在的情况。

结论:FTP代理服务器上,存在的近1000个客户端连接中(ESTABLISHED状态),有大量的假连接存在。也就是说,实际上这个连接早就断开不存在了,但服务端却还显示存在。

排查假 ESTABLISHED 连接

首先,如果出现假的 ESTABLISHED 连接,表示连接的客户端已经不存在了,客户端一方,要么发起了 TCP FIN 请求服务端没有收到,比如因为网络的各种原因(比如断网了)之后,FTP客户端无法发送FIN到服务端。要么服务端服务器接受到了 FIN,但是在后续过程中,丢包了等等。

为了验证上面的问题,我本机进行了一次模拟,连接FTP服务端后,本机直接断网,断网后,杀死FTP客户端进程,等待5分钟(为什么等待5分钟后面说)后,重新联网。然后再 FTP 服务端,查看服务器上与 FTP代理进行连接的所有IP,然后发现我本机的IP和端口依然在列,然后再我本机,通过

lsof -i :端口号

却没有任何记录,直接说明:服务端确实保持了假 ESTABLISHED 链接,一直不释放。

上面提到,我等待5分钟,是因为,服务端的 keepalive,是这样的配置:

[root@xx xx]# sysctl -a |grep keepalive
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 300

服务器默认设置的 tcp keepalive 检测是300秒后进行检测,也就是5分钟,当检测失败后,一共进行9次重试,每次时间间隔是75秒。
那么,问题就来了,服务器设置了 keepalive,如果 300 + 9*75 秒后,依然连接不上,就应该主动关闭假 ESTABLISHED 连接才对。为何还会积压呢?

猜想1:大量的积压的 ESTABLISHED 连接,实际上都还没有到释放时间

为了验证这个问题,我们就需要具体的看某个连接,什么时候创建的。所以,我找到其中一个我确定是假的 ESTABLISHED的链接(那个IP的用户,把所有FTP客户端都关了,进程也杀死了),看此连接的创建时间,过程如下:

先确定 FTP 代理进程的ID,为 61604

然后,看看这个进程的所有连接,找到某个端口的(55360,就是一个客户端所使用的端口)

[root@xxx xxx]# lsof -p 61604|grep 55360
server  61604 root    6u     IPv4 336087732      0t0        TCP node088032:ftp->192.168.70.16:55360 (ESTABLISHED)

我们看到一个 “6u”,这个就是进程使用的这个连接的socket文件,Linux中,一切皆文件。我们看看这个文件的创建时间,就是这个连接的创建时间了

ll /proc/61604/fd/6
//输出:
lrwx------. 1 root root 64 Nov  1 14:03 /proc/61604/fd/6 -> socket:[336087732]

这个连接是11月1号创建的,现在已经11月8号,这个时间,早已经超出了 keepalive 探测 TCP连接是否存活的时间。这说明2个点:

1、可能 Linux 的 KeepAlive 压根没生效。
2、可能我的 FTP 代理进程,压根没有使用 TCP KeepAlive

猜想2: FTP 代理进程,压根没有使用 TCP KeepAlive

要验证这个结论,就得先知道,怎么看一个连接,到底具不具备 KeepAlive 功效?

netstat 命令不好使(也可能我没找到方法),我们使用 ss 命令,查看 FTP进程下所有连接21端口的链接

ss -aoen|grep 192.168.12.32:21|grep ESTAB

从众多结果中,随便筛选2个结果:

tcp    ESTAB      0      0      192.168.12.32:21                 192.168.20.63:63677   ino:336879672 sk:65bb <->
tcp    ESTAB      0      0      192.168.12.32:21                 192.168.49.21:51896    ino:336960511 sk:67f7 <->

我们再对比一下,所有连接服务器sshd进程的

tcp    ESTAB      0      0      192.168.12.32:333                192.168.53.207:63269               timer:(keepalive,59sec,0) ino:336462258 sk:6435 <->
tcp    ESTAB      0      0      192.168.12.32:333                192.168.55.185:64892               timer:(keepalive,3min59sec,0) ino:336461969 sk:62d1 <->
tcp    ESTAB      0      0      192.168.12.32:333                192.168.53.207:63220               timer:(keepalive,28sec,0) ino:336486442 sk:6329 <->
tcp    ESTAB      0      0      192.168.12.32:333                192.168.53.207:63771               timer:(keepalive,12sec,0) ino:336896561 sk:65de <->

对比很容易发现,连接 21端口的所有连接,多没有 timer 项。这说明,FTP代理 进程监听 21 端口时,所有进来的链接,全都没有使用keepalive。

找了一些文章,大多只是说,怎么配置Linux 的 Keep Alive,以及不配置的,会造成 ESTABLISHED 不释放问题,没有说进程需要额外设置啊?难道 Linux KeepAlive 配置,不是对所有连接直接就生效的?

所以,我们有必要验证 Linux keepalive,必须要进程自己额外开启才能生效

验证 Linux keepalive,必须要进程自己额外开启才能生效

在开始这个验证之前,先摘取一段FTP中间件代理关于监听 21 端口的部分代码:

func (ftpServer *FTPServer) ListenAndServe() error {
    laddr, err := net.ResolveTCPAddr("tcp4", ftpServer.listenTo)
    if err != nil {
        return err
    }
    listener, err := net.ListenTCP("tcp4", laddr)
    if err != nil {
        return err
    }
    for {
        clientConn, err := listener.AcceptTCP()
        if err != nil || clientConn == nil {
            ftpServer.logger.Print("listening error")
            break
        }
        //以闭包的方式整理处理driver和ftpBridge,协程结束整体由GC做资源释放
        go func(c *net.TCPConn) {
            driver, err := ftpServer.driverFactory.NewDriver(ftpServer.FTPDriverType)
            if err != nil {
                ftpServer.logger.Print("Error creating driver, aborting client connection:" + err.Error())
            } else {
                ftpBridge := NewftpBridge(c, driver)
                ftpBridge.Serve()
            }
            c = nil
        }(clientConn)
    }
    return nil
}

足够明显,整个函数,net.ListenTCP 附近都没有任何设置KeepAlive的相关操作。我们查看 相关函数,找到了设置 KeepAlive的地方,进行一下设置:

if err != nil || clientConn == nil {
    ftpServer.logger.Print("listening error")
    break
}
// 此处,设置 keepalive
clientConn.SetKeepAlive(true)

重新构建部署之后,可以看到,所有对21端口的连接,全部都带了 timer

ss -aoen|grep 192.168.12.32:21|grep ESTAB

输出如下:

tcp    ESTAB      0      0      192.168.12.32:21                 192.168.70.76:54888               timer:(keepalive,1min19sec,0) ino:397279721 sk:6b49 <->
tcp    ESTAB      0      0      192.168.12.32:21                 192.168.37.125:49648              timer:(keepalive,1min11sec,0) ino:398533882 sk:6b4a <->
tcp    ESTAB      0      0      192.168.12.32:21                 192.168.33.196:64471              timer:(keepalive,7.957ms,0) ino:397757143 sk:6b4c <->
tcp    ESTAB      0      0      192.168.12.32:21                 192.168.21.159:56630              timer:(keepalive,36sec,0) ino:396741646 sk:6b4d <->

可以很明显看到,所有的连接,全部具备了 timer 功效,说明:想要使用 Linux 的 KeepAlive,需要程序单独做设置进行开启才行。

最后:ss 命令结果中 keepalive 的说明

首先,看一下 Linux 中的配置,我的机器如下:

[root@xx xx]# sysctl -a |grep keepalive
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 300

tcp_keepalive_time:表示多长时间后,开始检测TCP链接是否有效。
tcp_keepalive_probes:表示如果检测失败,会一直探测 9 次。
tcp_keepalive_intvl:承上,探测9次的时间间隔为 75 秒。

然后,我们看一下 ss 命令的结果:

ss -aoen|grep 192.168.12.32:21|grep ESTAB
tcp ESTAB  0  0  192.168.12.32:21 192.168.70.76:54888 timer:(keepalive,1min19sec,0) ino:397279721 sk:6b49 <->

摘取这部分:timer:(keepalive,1min19sec,0) ,其中:

keepalive:表示此链接具备 keepalive 功效。
1min19sec:表示剩余探测时间,这个时间每次看都会边,是一个递减的值,第一次探测,需要 net.ipv4.tcp_keepalive_time 这个时间倒计时,如果探测失败继续探测,后边会按照 net.ipv4.tcp_keepalive_intvl 这个时间值进行探测。直到探测成功。
0:这个值是探测时,检测到这是一个无效的TCP链接的话已经进行了的探测次数。


欢迎关注“海角之南”公众号获取更新动态

haijiaozhinan.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容