☰
大量FIN_WAIT1连接堆积:成因、内核参数与应用层排查
2026/10/26 11:07:35 网站建设 项目流程

1. 大量 FIN_WAIT1 为什么值得单独拎出来说

运维干久了会发现,大部分 TCP 状态问题都是有固定套路的:TIME_WAIT 多了调端口范围,CLOSE_WAIT 多了骂应用没关连接。但 FIN_WAIT1 是个例外,它出现的频率没有前两个高,一旦真的堆起来,往往意味着连接的另一端在某个环节彻底失联了,而且失联的位置很难一眼看出来。我见过不止一次,一台机器上ss -s显示几千个 FIN_WAIT1,业务侧的报错却不明显,只是接口偶发超时,等真正发现的时候,连接池已经被这些不存在的连接占满了端口和文件描述符。

先说清楚这个东西是什么。FIN_WAIT1 是 TCP 四次挥手的第一站,只有主动关闭方才会进入这个状态:本端调用close()或者shutdown(SHUT_WR),内核把发送缓冲区里的数据发完,再发一个 FIN 出去,然后进入 FIN_WAIT1,等对方的 ACK。正常情况下这个等待是毫秒级的,ACK 一回来就进 FIN_WAIT2,所以你在线上随手抓一台健康机器的状态,FIN_WAIT1 的数量应该长期是个位数甚至零。一旦它变成几百上千并且持续存在,说明这些 FIN 发出去以后,没有任何回应回来。

这篇文章适合谁看:做后端服务、网关、代理转发、消息推送、数据库中间件这类需要大量发起网络连接的场景,尤其是那些"我们只是调用方"的团队。因为主动关闭方才是产生 FIN_WAIT1 的一方,很多时候问题不在你这台机器,但背锅的是你。下面我会把状态机、内核参数、应用代码、排查手法拆开讲,最后给一份可以直接照着做的速查清单。

1.1 四次挥手和 FIN_WAIT1 的位置关系

把状态机摊开看会更清楚。TCP 连接关闭不是一次握手,而是一个双向的、独立的过程,两个方向各发一个 FIN,各回一个 ACK,总共四次报文。主动关闭的一方走的是这样一条路径:

阶段本端动作本端状态等待什么
1应用调用 close(),内核发出 FINFIN_WAIT1对方对本端 FIN 的 ACK
2收到 ACKFIN_WAIT2对方发来的 FIN
3收到对方 FIN,回 ACKTIME_WAIT等 2MSL 后彻底释放
4超时或收到 RSTCLOSED无

FIN_WAIT1 卡住,意思就是第 1 步和第 2 步之间的那个 ACK 一直没回来。这个 ACK 由对端内核发出,跟对端应用的业务逻辑无关,按理说是链路层的事,网络通就一定会回。所以能卡在这个状态,原因基本收敛到三类:报文出不去、ACK 回不来、或者 FIN 压根还没发出去。

第三类特别容易被忽略,我在后面会专门用一节讲。很多人一看 FIN_WAIT1 就去查防火墙,查了半天发现是应用层在close()之前还往 socket 里写了几个 G 的数据,发送窗口早满了,FIN 排在那堆数据后面干等着。

1.2 FIN_WAIT1 和 CLOSE_WAIT 的本质区别

这两个状态经常被混为一谈,因为表现都是"连接数量异常多",但根因和处置方向完全相反。

对比项FIN_WAIT1CLOSE_WAIT
谁进入这个状态主动关闭方(发起 close 的一方)被动关闭方(收到对方 FIN 的一方)
内核在等什么等对方的 ACK等本端应用调用 close()
谁该负责修复网络链路、对端服务、本端发送逻辑本端应用代码,没关连接
内核能否自动清理能,重传耗尽后发 RST 释放不能,只要应用不 close,永远挂着
典型数量级通常是 0 到几十经常几千上万

看这张表就能明白为什么 FIN_WAIT1 更麻烦:CLOSE_WAIT 的锅很明确,翻代码找那个没关的finally块就行,属于可控范围内的问题。FIN_WAIT1 则是"我已经仁至义尽地把关闭请求发出去了,对面不理我",你能做的事情少得多,而且中间可能隔着负载均衡、NAT 网关、安全设备,任何一环把 FIN 或者 ACK 丢了,你都会看到同样的现象。

我个人的经验判断是:如果 CLOSE_WAIT 占比高,先查代码;如果 FIN_WAIT1 占比高,先查链路和对端存活状态,再回头看自己的发送逻辑。

1.3 一个关键细节:FIN 可能还在排队没发出去

这是最容易踩的坑,必须单独说。TCP 是字节流协议,FIN 在协议栈里被当作"占用一个序号的控制报文",它必须排在所有已提交数据之后。也就是说,如果你在close()之前写入了 10MB 数据,而对端的接收窗口因为应用处理慢只开了 64KB,那么发送缓冲区会堆积大量未确认数据,FIN 会一直排在队尾,内核根本还没把它发到网络上。

这时候ss看到的确实是 FIN_WAIT1,但你抓包会发现网络上根本没有 FIN 报文。很多人抓着包找了半天,怀疑是中间设备丢包,其实是被自己的状态迷惑了。区分方法很简单:

# 看这个连接发送队列里还有多少字节没发出去 ss -tani state fin-wait-1 # 输出里的 Send-Q 就是发送队列未确认/未发送的字节数 # 如果 Send-Q 是个很大的正数,说明 FIN 还排在数据后面

Send-Q非零且很大,说明是应用层的写入量超过了链路和对端的消化能力,FIN 被困在本地队列。Send-Q为 0 但状态还是 FIN_WAIT1,才是真正的"FIN 已发出、ACK 未回"。这两种情况的排查方向完全是两条路,先分清再动手,能省掉大量无效排查。

注意:ss -tani输出里的Send-Q在不同内核版本含义略有差异,有的版本显示的是已排队未发送字节数,有的包含未确认字节。看趋势比看绝对值更有意义,隔几秒采样两次,数值一直不下降就是真的堵住了。

2. 内核参数怎么调,哪些真有用哪些是玄学

打开搜索引擎搜 FIN_WAIT1,十篇文章有八篇让你改net.ipv4.tcp_fin_timeout。这个建议是错的,至少是不完整的。我在下面把几个常被提及的参数挨个拆开讲,说清楚它们各自管的是哪一段,避免你调完一圈发现毫无变化。

2.1 tcp_fin_timeout 管的是 FIN_WAIT2,不是 FIN_WAIT1

这个参数名字太有迷惑性了。tcp_fin_timeout默认值 60,它的实际作用对象是FIN_WAIT2 状态,也就是本端发完 FIN 并且收到了对方的 ACK 之后,等对方发 FIN 过来的这段超时。对于已经调用过close()的孤儿连接,Linux 从 4.x 某个版本开始用tcp_orphan_retries来管理生命周期,tcp_fin_timeout的作用进一步被削弱。

换句话说,你在一个全是 FIN_WAIT1 的机器上把tcp_fin_timeout从 60 改成 30,大概率什么都不会发生。这个参数改了以后不会立即生效于已存在的连接,只影响新进入 FIN_WAIT2 的连接,而且如果你的对端压根不响应,FIN_WAIT1 的重传节奏跟这个参数毫无关系。

真正决定孤儿套接字在 FIN_WAIT1 停留多久的,是net.ipv4.tcp_orphan_retries。它的默认值是 0,内核会把它当作 8 来处理,也就是允许重传 8 次。按照 TCP 的指数退避,8 次重传的总时长通常在几分钟的量级,具体取决于当时的 RTT 和 RTO 计算。重传次数用尽后,内核会直接发 RST 把连接强行拆掉,并释放套接字资源,同时在TcpExtTCPAbortOnTimeout计数器上加一。

# 查看当前值 sysctl net.ipv4.tcp_orphan_retries sysctl net.ipv4.tcp_fin_timeout # 观察因超时被强制中断的连接数 nstat -az | grep -i -E "AbortOnTimeout|AbortOnMemory|TCPTimeouts"

TcpExtTCPAbortOnTimeout这个数字如果持续快速增长,说明系统里有一批连接在重传耗尽后被内核强制拆了,这正是大量 FIN_WAIT1 最终的归宿。它是判断问题是否在"自动自愈"的重要依据:如果这个数一直在涨,说明内核在帮你兜底,虽然难看但不至于资源泄漏;如果这个数长期不动而 FIN_WAIT1 数量居高不下,那才是真麻烦,说明重传还没走完,或者套接字压根不是孤儿状态。

2.2 tcp_max_orphans 是内存保护而非状态清理

net.ipv4.tcp_max_orphans限制的是全系统孤儿套接字的总数。所谓孤儿套接字,就是应用已经调用close()放开文件描述符、但内核还保留着 TCP 控制块在重传的连接。这类套接字不占用进程的 fd,但实实在在占用内核内存,所以需要一个上限来兜底。

这个值默认跟机器的物理内存挂钩,一般机器上是几万。一旦超过上限,内核会打印Out of socket memory到 dmesg,并开始主动重置新的孤儿连接。很多"FIN_WAIT1 数量涨到某个值就卡住不动了"的现象,就是撞到了这个天花板。

# 查看当前孤儿套接字数量 cat /proc/net/sockstat # 输出类似 # sockets: used 1024 # TCP: inuse 300 orphan 5 tw 200 alloc 900 mem 30 # orphan 那一项就是当前孤儿套接字数量

调大tcp_max_orphans并不能解决问题,只能让堆积的阈值更高、内存消耗更大,本质上是用更大的隐患换更长的观察时间。我的建议是不要动它,把它当成一个保护阈值来看待:一旦orphan数量接近上限,说明问题已经很严重了,赶紧去查根因,而不是继续调大。

2.3 发送缓冲区相关的两个参数

既然 FIN 可能被困在发送队列里,那么发送缓冲区的大小就直接影响 FIN_WAIT1 的持续时间。相关参数有两个:

# 发送缓冲区默认值和上限 sysctl net.ipv4.tcp_wmem # 典型输出:4096 16384 4194304 # 分别是最小值、默认值、最大值(字节) # 自动调整开关,1 表示开启 sysctl net.ipv4.tcp_moderate_rcvbuf

tcp_wmem的第三个值是单个连接发送缓冲区的上限,也就是 4MB。这意味着在最坏情况下,一个连接里可能积压 4MB 的数据还没发出去,而 FIN 就排在这 4MB 后面。如果对端的接收能力很差,这 4MB 数据要重传很久,FIN_WAIT1 就会持续很久。

把tcp_wmem调小可以在一定程度上限制单个连接的积压量,让 FIN 更早被发出去,但代价是吞吐下降,尤其是在高带宽延迟积的链路上。这是一个明确的权衡,不是无脑调优。我在实际项目里更倾向于不动全局参数,而是在应用层控制单次写入的数据量,这个后面讲。

注意:修改tcp_wmem会同时影响所有 TCP 连接,包括那些吞吐敏感的业务。如果机器上跑着多种业务,全局调参几乎必然误伤某一方,动手前务必确认机器用途单一。

2.4 参数调整的实操步骤与验证方法

参数调整不是拍脑袋改数字,得有基线、有验证、有回滚。我常用的流程是这样:

第一步,先记录现状。至少包含五项:ss -s的总览、ss -tan state fin-wait-1 | wc -l的计数、/proc/net/sockstat里的 orphan 数量、nstat -az里几个关键计数器的快照、以及一份持续 60 秒的采样曲线。

# 一分钟采样,每 5 秒记一次 for i in $(seq 1 12); do echo "$(date +%H:%M:%S) fin_wait1=$(ss -tan state fin-wait-1 | wc -l) orphan=$(awk '/^TCP:/{print $5}' /proc/net/sockstat)" sleep 5 done

第二步,只改一个参数。同时改三个参数,出了问题你根本不知道是哪个引起的。

第三步,改完观察至少一个完整的重传周期。如果你改了tcp_orphan_retries,就要等够重传耗尽所需的时间,通常要十几分钟,只看两分钟就下结论是没有意义的。

第四步,准备好回滚脚本。sysctl -w改的值重启就没了,但如果写进了/etc/sysctl.conf,一定要留一份原始备份。

# 保守一点的做法:把关键网络参数快照存档 sysctl -a 2>/dev/null | grep -E "^net\.ipv4\.(tcp|ip)" > /root/net-params-$(date +%Y%m%d).bak

我个人在这类问题上的一贯态度是:内核参数只做兜底,不做根治。能用应用层解决的事情,不要动全局参数。因为网络参数的影响面太广,而应用层的问题定位更精确、回滚也更简单。

3. 应用层才是真正的战场

把内核参数全部捋一遍之后你会发现,能调的其实不多,而且大多数是"让资源耗尽得慢一点"而不是"让问题消失"。真正能根治的地方在应用代码和网络拓扑上,这一节讲几个我踩过并且确认有效的点。

3.1 短连接洪水与连接池滥用

大量 FIN_WAIT1 最常见的前置场景,是短时间内创建了海量短连接。比如某个服务每次请求都新建 HTTP 连接,QPS 上来以后,每秒产生几千个连接,每个连接用完立刻close(),于是每秒就有几千个连接进入 FIN_WAIT1。只要其中一小部分对端响应慢或者链路抖动,这些连接就会滞留。

这里有个反直觉的点:短连接的 FIN_WAIT1 数量跟对端的响应速度关系极大。对端正常时 ACK 秒回,FIN_WAIT1 存在时间可能只有几毫秒,采样都采不到;对端一旦卡顿,FIN_WAIT1 就会立刻堆积。所以看到 FIN_WAIT1 暴涨的第一反应,应该是去看对端的健康度,而不是先怀疑自己的机器。

处置方向很清晰:能用长连接就用长连接,必须用短连接就给并发加限流。

# 一个粗糙但有效的思路:用信号量限制同时创建中的连接数 import threading import time class LimitedDialer: def __init__(self, max_concurrent=200, timeout=3.0): self._sem = threading.BoundedSemaphore(max_concurrent) self._timeout = timeout def connect(self, addr): # 拿不到令牌就直接失败,避免无限制堆积 if not self._sem.acquire(timeout=self._timeout): raise RuntimeError("connect limited: too many in-flight") try: return self._do_connect(addr) finally: self._sem.release() def _do_connect(self, addr): # 占位,实际使用对应语言的连接库 time.sleep(0.001) return addr

这个模式的核心不是这段代码本身,而是限制"正在建立中"的连接数量这个思路。很多连接池只限制了空闲连接数和总连接数,但对"正在三次握手或四次挥手过程中的连接"没有约束,这恰恰是 FIN_WAIT1 的来源。

3.2 写超时缺失,是 FIN 发不出去的元凶之一

前面说过,close()之后如果发送缓冲区还有数据,FIN 会排在后面。默认情况下,send()在缓冲区满时会阻塞,且没有超时。如果对端一直不开窗口,你的应用线程就会永久阻塞在send()上,连接既发不完数据也关不掉。

解药是给 socket 设置发送超时:

import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 发送超时 5 秒,超时后 send 抛异常,连接走异常关闭路径 s.setsockopt(socket.SOL_SOCKET, socket.SO_SNDTIMEO, 5) s.connect(("10.0.0.1", 8080))

设了发送超时之后,send()在超时后会抛socket.timeout,此时如果你直接让异常冒泡并让对象被回收,内核会走 RST 关闭路径,连接会立刻从 FIN_WAIT1 消失,而不是慢慢等重传耗尽。

另一种更温和的做法是主动缩短单次写入的数据量,把大块数据拆成小包分批写,中间检查是否还能继续写。这样即使对端慢,也不会让 FIN 无限期地排在队尾。

3.3 中间设备静默丢包,最难缠的一类

这是我最头疼的场景:本端 socket 一切正常,应用也有超时设置,抓包显示 FIN 确实发出去了,但 ACK 就是回不来。查来查去,问题出在中间的网络设备上——常见的形态是会话表老化、连接跟踪表满了之后新连接直接丢弃、或者某些设备对带 FIN 标志的报文有特殊处理。

判断这类问题的方法:

第一,确认 FIN 是否真的发出去了。在出方向的网卡上抓包,看有没有Flags [F.]的报文。有,说明本端尽力了。

# 抓本端出方向的 FIN 报文 tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-fin != 0 and dst host 10.0.0.1' -c 20

第二,确认对端是否收到了。这需要登录对端或者在链路中间抓包。如果对端抓不到 FIN,说明中间丢了;如果对端抓到了并且回了 ACK,但本端没收到 ACK,说明回程丢了。两种情况的责任方不同。

第三,检查会话表。如果你的连接要经过状态防火墙或者网络地址转换设备,这些设备通常有会话老化时间,一般 TCP 会话是几分钟到几十分钟。如果你的应用维持的是长连接但中间长时间无数据,会话可能已被老化掉,此时你发 FIN 出去,设备查不到会话,直接静默丢弃,ACK 自然也不会回来。

应对手段有两条:一是在连接上开启 TCP keepalive,用周期性小包维持会话;二是把应用层心跳周期设得比会话老化时间更短。

# 开启 keepalive 并缩短探测周期(Linux 全局参数) sysctl -w net.ipv4.tcp_keepalive_time=120 sysctl -w net.ipv4.tcp_keepalive_intvl=30 sysctl -w net.ipv4.tcp_keepalive_probes=3

这套配置的意思是:连接空闲 120 秒后开始探测,每 30 秒一次,连续 3 次无响应就判定连接死亡。相比默认的 7200 秒开始探测,它能早得多地发现问题。但别忘了,tcp_keepalive_time是全局的,会影响机器上所有连接。

3.4 已经堆积的连接怎么快速清理

问题已经发生了,堆积了几千个 FIN_WAIT1,业务端口快耗尽了,这个时候光调参数没用,因为参数影响的是新连接。有几种应急手段,各有代价。

第一种是等。前面说过,孤儿套接字重传耗尽后内核会发 RST,通常几分钟到十几分钟会自动清掉一批。可以观察TcpExtTCPAbortOnTimeout是否在增长,如果增长说明在自愈。

第二种是重启进程。所有属于该进程的连接会被一并清理,这是最快的手段,但业务要中断。

第三种是调整路由或者防火墙,让 FIN 的重传更快失败。比如在测试环境下临时把目标地址 blackhole 掉,内核会立刻收到不可达,连接直接进入 CLOSED。线上环境慎用,这是杀敌一千自损八百。

# 应急:查看属于某进程的 FIN_WAIT1 连接 ss -tanp state fin-wait-1 | grep -F "pid=12345" # 统计某进程的 FIN_WAIT1 数量 ss -tanp state fin-wait-1 | grep -cF "pid=12345"

我更推荐的做法是提前做好防护:给业务设置连接数上限,让它在接近上限时主动拒绝新请求,而不是把端口和内存全部耗光后整体雪崩。软性的降级总好过硬性的崩溃。

4. 现场排查的完整手法与一次真实复盘

理论讲完了,这一节上点实操。我把自己的排查流程固化成了一个顺序,从宏观到微观,每一步都能收敛一部分可能性,避免东一榔头西一棒子。

4.1 三分钟初筛:先搞清楚量级和分布

任何连接状态问题的第一步都是看量和分布,不要一上来就抓包。抓包成本高、信息密度低,适合在有明确怀疑对象之后用。

# 总数和分状态统计 ss -s # FIN_WAIT1 的详细列表,重点关注 Recv-Q / Send-Q ss -tani state fin-wait-1 | head -50 # 按对端地址聚合,看是不是集中在某几个目标上 ss -tan state fin-wait-1 | awk 'NR>1 {print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head

最后那条聚合命令非常关键。如果 FIN_WAIT1 的连接集中在一两个对端 IP 上,那基本可以锁定是对端服务或者通往对端的链路出了问题;如果分散在几百个不同的目标地址上,那更可能是本端机器自身的出口链路或者本地资源问题。这一个判断就能砍掉一半的排查方向。

我习惯的初筛顺序是:先看分布(集中还是分散)→ 再看 Send-Q(FIN 发出去了没)→ 再看进程归属(是不是某个服务独有)→ 最后看内核计数器(是否在自愈)。四步走完,通常五分钟内能定位到大致方向。

4.2 深入到内核计数器

ss只能看到快照,内核的计数器才能反映趋势。nstat比netstat -s好用,因为它可以给差值。

# 首次快照 nstat -az > /tmp/nstat.1 # 等 60 秒 sleep 60 # 第二次快照并计算差值 nstat -az > /tmp/nstat.2 nstat -z -f /tmp/nstat.2

需要重点关注这几项:

计数器含义异常表现
TcpExtTCPAbortOnTimeout重传耗尽被强制中断的连接数持续增长说明在自愈,不增长则相反
TcpExtTCPAbortOnMemory因内存超限被中断的连接数非零说明撞到了 tcp_max_orphans
TcpOutRsts发出的 RST 数突增可能与批量断连相关
TcpRetransSegs重传报文数占总发送比例过高说明链路质量差
TcpExtTCPTimeoutsTCP 超时事件数配合上面几项判断是偶发还是持续

TcpExtTCPAbortOnMemory非零是一个非常明确的信号:系统已经在动用保护机制主动拆连接了。这时候不是调参数的问题,是必须立刻降低短连接创建速率的问题。

4.3 一个完整的复盘

讲个我印象最深的案例。某业务线的接口偶发超时告警,比例不高,千分之几,但持续了三天。监控上指标都正常,只有一台机器的 FIN_WAIT1 数量在缓慢爬升,从个位数涨到了两千多。

第一轮排查,先看聚合分布。发现这两千多个连接集中在三个对端地址上,比例超过 90%。这基本确认了问题不在本机,是通往这几个目标的链路有问题。

第二轮,看Send-Q。全部为 0。说明 FIN 已经发到网络上了,卡在等 ACK,排除了发送队列积压这个可能。

第三轮,抓包。在本机出口抓到 FIN 报文确实发出了,频率跟连接建立频率一致,但没有任何对应的 ACK 回来。同时观察到,同一时间段内到这三个地址的新建连接有一部分也超时。

第四轮,找网络侧确认。结果是中间有一台转发设备的会话表容量有限,在业务高峰期被其他流量占满,新会话建不进去,且设备对已有会话的报文处理出现丢包。表现就是新建连接超时加上已有连接的 FIN 得不到 ACK。

最终的处置方案分三层:业务侧把到这三个目标的短连接改成长连接加连接池,连接复用率提上去以后,新建连接的速率下降了接近两个数量级;同时把应用层的连接超时从 30 秒压到 5 秒,让失败快速暴露而不是拖延;网络侧扩容了那台设备的会话表。

这个案例里,内核参数一个都没改。因为改了也没用——重传耗尽的兜底机制一直在工作,只是连接产生的速度超过了清理的速度。真正解决问题的永远是减少连接的创建频率和缩短失败路径,而不是让内核清理得更快。

4.4 修复后的验证方法

改完之后怎么确认真的好了?不能只看"没报警了"。我一般会做三件事。

第一,做一次压测,把 QPS 拉到峰值的 1.5 倍,持续 30 分钟,同时持续采样 FIN_WAIT1 数量。健康的曲线应该是围绕一个低值上下波动,而不是单调上升。

第二,人为注入一次故障,比如临时阻断到某个目标的网络,观察 FIN_WAIT1 的爬升速度和回落速度。爬升慢说明连接创建速率被控制住了,回落快说明重传兜底机制正常。这个测试能验证你的防护措施是否真的有效。

第三,确认内核计数器没有新的异常,特别是TcpExtTCPAbortOnMemory必须保持为零。

5. 速查表与几条踩出来的经验

5.1 问题速查表

现象最可能的原因优先排查动作
FIN_WAIT1 集中在少数对端 IP对端服务异常或链路问题直接联系对端确认存活,或用 tcpdump 抓 FIN 是否发出
FIN_WAIT1 分散在大量不同目标本机出口链路或本地资源问题检查本机网卡错误计数、路由、上游设备
FIN_WAIT1 且 Send-Q 大量非零应用写入量超过链路消化能力检查应用单次写入大小、是否缺写超时
FIN_WAIT1 数量涨到固定值不动撞到 tcp_max_orphans 上限查 dmesg 是否有 Out of socket memory
FIN_WAIT1 伴随 TcpExtTCPAbortOnTimeout 暴涨内核正在重传耗尽后强制清理属于自愈过程,重点转为降低连接创建速率
FIN_WAIT1 与 CLOSE_WAIT 同时高关闭流程两端都有问题先修 CLOSE_WAIT(代码问题),再看 FIN_WAIT1

5.2 几条血泪经验

第一条,别在症状上做优化。我见过团队把tcp_fin_timeout、tcp_orphan_retries、tcp_max_orphans全调了一遍,FIN_WAIT1 数量一点没降,因为对端根本就没响应,调本端参数只是让内核拆得更早,但连接还在被源源不断地创建出来。调参之前先问一句:这些连接是从哪来的,为什么这么多?

第二条,先怀疑对端,再怀疑自己。主动关闭方是受害者,这是 TCP 状态机的客观事实。你们的监控系统如果只看本机指标,很容易把责任划错。我建议在告警规则里加一条关联判断:如果 FIN_WAIT1 集中度超过 80%,告警文案直接提示"疑似对端异常",减少一线的误判时间。

第三条,给连接加超时比调内核有用一百倍。不管是连接超时、读超时还是写超时,只要有明确的上限,出问题时连接就会快速失败并释放,而不是无限期挂着。这一条我在多个不同类型的业务上都验证过,投入产出比最高。

第四条,上线前做一次网络异常演练。具体做法是:在测试环境把服务的出口流量做一定比例的丢包,然后看 FIN_WAIT1 的数量变化和系统的自愈能力。这个演练能提前暴露出连接池配置不合理、缺少超时、重试策略过于激进等一系列问题,比等线上出事再复盘划算得多。

第五条,保留现场的意识。FIN_WAIT1 这类问题的复现成本很高,一旦出现,第一时间把ss -tani state fin-wait-1的完整输出、nstat -az快照、dmesg尾部、以及出方向的抓包都存下来。等事后分析的时候,这些数据就是唯一的线索。我现在的做法是写进自动化脚本,一条命令打包所有现场信息,出事的时候不用现想该收集什么。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询