我的服务平时稳如老狗,可每次大促整点开闸流量一瞬间涌进来就有一批用户连接直接超时连不上、而服务端应用日志干干净净没有任何报错 CPU 内存也都没满,我盯着监控百思不得其解,最后才挖出来是 TCP 三次握手完成后那个全连接 accept 队列被挤满了、新完成握手的连接被内核静默丢弃了的深度复盘

我有个接口平时 QPS 不高服务稳得很,可每次运营搞活动整点开闸的那一瞬间,大量用户几乎同一秒涌进来,就有一批人连不上——页面打不开、一直转圈最后超时、刷新几次又好了。我上去看监控越看越糊涂:连不上的请求在我的应用日志里根本没有任何记录、就像从没到达过应用,CPU 内存也就五六成远没满,下游 DB 全绿,客户端报的是 connect timeout 连接阶段就超时了、有的显示 SYN 重传几次放弃,而且峰值一过后到的用户又都正常。服务没崩资源没满日志没错,可偏偏流量瞬间涌入时有一批连接连应用的门都没进来就超时了。请求没进应用,那一定是在应用之下的内核网络栈被拦下的。补了一遍 TCP 握手才搞懂:三次握手完成 ≠ 应用马上拿到连接,内核为监听套接字维护两个队列——半连接队列(SYN queue,收到 SYN 回 SYN-ACK 等客户端 ACK)和全连接队列(accept queue,三次握手已完成进入 ESTABLISHED 等应用 accept() 取走);握手完成的连接先进全连接队列排队、再由应用 accept() 逐个取走,这个队列容量有限约为 min(应用 listen 传入的 backlog, 内核 net.core.somaxconn) 很多默认才 128;当连接涌入速度超过应用 accept 取走速度、队列被填满,内核对新完成握手连接的默认行为(tcp_abort_on_overflow=0)是静默丢弃——不发 RST 不报错只装作没收到那个 ACK,于是客户端等不到回应 connect 超时,而应用因为这些连接从未被 accept 故日志里毫无痕迹。netstat -s 看 listen queue overflowed 计数哗哗涨、ss -lnt 看到 Recv-Q 顶满 Send-Q(128),实锤是全连接队列溢出。正解两手抓:调大队列容量(内核 somaxconn 和应用层 backlog 如 Netty SO_BACKLOG、Tomcat accept-count、Nginx listen backlog 必须一起调大、单调内核没用因为实际容量取两者较小值),更要提高应用 accept 与处理速度(accept 线程与业务线程分离、业务用独立线程池、可预期突发提前扩容预热),并给队列溢出加监控告警。这篇复盘从故障现场讲到全连接队列满了静默丢连接、半连接与全连接队列对照、怎么诊断,再到调容量加提速的完整正解与压测清单,以及线程池任务队列/消息队列积压/UDP 与网卡接收缓冲等同类有限缓冲静默溢出的坑,和正视缓冲的有限、消除缓冲的盲区给满和丢弃加监控的认知。

我的服务平时稳如老狗,可每次大促开闸流量一涌进来,就有一批用户连接直接超时连不上、而服务端日志干干净净没有任何报错,CPU 内存也都没满,我盯着监控百思不得其解,最后才挖出来是 TCP 握手完成后的那个全连接队列(accept 队列)被挤满了、新连完成三次握手的连接被内核静默丢弃了的深度复盘

这是我做后端这些年,排查得最憋屈的一次——憋屈在于,出事的时候,所有该报警的地方都安安静静,所有日志都干干净净,仿佛什么都没发生,可用户那边就是连不上

故障现场:一开闸就有人连不上,服务端却毫无异常

我们有个接口,平时 QPS 不高,服务跑得稳稳的,几个月都没出过事。问题出在每次运营搞活动、整点开闸的那一瞬间:大量用户几乎在同一秒涌进来,然后客服那边就开始收到反馈——"页面打不开"、"一直转圈最后提示超时"、"刷新几次又好了"

我第一反应是服务扛不住了,赶紧上去看监控,结果越看越糊涂:

  • 服务端的应用日志干干净净:那些连不上的请求,在我的应用日志里根本没有任何记录——不是报错,而是压根没出现过,就好像这些请求从来没到达过我的应用。
  • CPU、内存都没满:机器负载也就五六成,远没到瓶颈,GC 也正常,线程池也没打满。
  • 下游、数据库都正常:依赖的服务和 DB 监控全绿,响应时间也没波动。
  • 客户端报的是连接超时:不是业务报错(比如 500),而是连接阶段就超时了——connect timeout,有的客户端日志显示 SYN 重传了好几次最后放弃。
  • 过几秒就自己好了:开闸那一下最严重,峰值过去后,后到的用户又都能正常连上了。

这就很诡异了:服务没崩、资源没满、日志没错,可偏偏在流量瞬间涌入时,有一批连接连应用的门都没进来就超时了。请求没到应用,那它是在哪一层被拦下的?既然应用毫无感知,那一定是在应用之下、内核网络栈这一层出了事。我开始把注意力从应用代码,转向了 TCP 连接建立的过程本身。

第一件事:搞懂 TCP 握手完成后,连接要先在内核的"全连接队列"里排队等应用来取

顺着"请求没进应用就没了"这条线,我重新去补了一遍 TCP 三次握手在内核里到底发生了什么,这才发现一个我以前完全忽略的细节——三次握手成功 ≠ 应用马上就能处理这个连接。中间还隔着两个内核队列。

当一个服务 listen() 在某个端口上时,Linux 内核为这个监听套接字维护着两个队列:

  1. 半连接队列(SYN queue):服务端收到客户端的 SYN、回了 SYN-ACK、但还没收到客户端最后那个 ACK 的连接,放在这里(状态 SYN_RECV)。它们是"握手进行到一半"的连接。
  2. 全连接队列(accept queue,也叫 accept backlog):三次握手已经全部完成(收到了客户端的 ACK、连接进入 ESTABLISHED)、正在等待应用程序调用 accept() 把它取走的连接,放在这里。

关键就在这第二个队列。我原以为"握手一完成,我的应用就立刻拿到连接了",但实际上不是:握手完成的连接,是先被内核放进全连接队列里排队,然后等应用的 accept() 来一个一个取走的。如果应用 accept() 的速度,跟不上新连接涌入、堆进队列的速度,这个队列就会越积越多

而队列是有容量上限的。全连接队列的最大长度,大致是 min(应用传给 listen() 的 backlog 参数, 内核参数 net.core.somaxconn) 这两者里的较小值。一旦全连接队列满了,此时再有连接完成三次握手要进队列,内核的默认行为(net.ipv4.tcp_abort_on_overflow=0 时)是——把这个刚完成握手的连接,静默地丢弃:内核不发 RST、不报错,只是装作没收到客户端那个 ACK,把这次入队"当没发生"。

这下所有诡异现象全说通了。我先用一行命令确认了这个推断——看队列溢出的计数:

# 看是否有连接因队列溢出被丢弃(关键证据)
# "times the listen queue of a socket overflowed" 这个计数在持续上涨就实锤了
netstat -s | grep -i -E "listen|overflow"
# 典型输出:
#   12847 times the listen queue of a socket overflowed
#   12847 SYNs to LISTEN sockets dropped

# 实时看某监听端口的全连接队列:Recv-Q=当前排队数, Send-Q=队列最大容量
# 如果 Recv-Q 持续逼近甚至等于 Send-Q,就是队列要满/已满
ss -lnt 'sport = :8080'
# State   Recv-Q  Send-Q  Local Address:Port
# LISTEN  129     128     0.0.0.0:8080      <- Recv-Q(129)>Send-Q(128),已溢出!

# 看内核两个相关参数当前值
sysctl net.core.somaxconn          # 全连接队列上限的硬顶
sysctl net.ipv4.tcp_max_syn_backlog # 半连接队列上限

果然,netstat -s 里那个 "times the listen queue of a socket overflowed" 的计数,在每次开闸的瞬间哗哗往上涨;ss -lnt 看过去,开闸时那个端口的 Recv-Q(当前排队数)死死顶在 Send-Q(队列容量,显示是 128)上。真相大白:不是我的服务扛不住,而是开闸瞬间海量连接同时完成握手、涌进那个只有 128 容量的全连接队列,而我的应用 accept() 取走连接的速度一时跟不上,队列瞬间被挤爆,后面完成握手的连接就被内核静默丢弃了。客户端这边,发完最后的 ACK 以为连接建好了(或者还在等 SYN-ACK),却迟迟等不到服务端的回应,只好 SYN 重传、重传几次还不行,最终 connect 超时——而我的应用,因为这些连接根本没被 accept() 取走过,自然在日志里不会有任何它们的痕迹

第二件事:正解——调大队列容量,更要提高应用 accept 和处理的速度

找到根因,解法就有了清晰的两个方向:一是把队列这个"缓冲"扩大,二是把应用"消费"连接的速度提上去。两者要配合,光扩队列治标不治本。

先说扩容这一侧——这是最快能止血的:

# 1) 调大内核全连接队列硬顶 somaxconn(默认很多发行版才 128!)
sysctl -w net.core.somaxconn=4096
# 持久化:写进 /etc/sysctl.conf 或 /etc/sysctl.d/xxx.conf
echo "net.core.somaxconn = 4096" >> /etc/sysctl.conf

# 2) 调大半连接队列(应对 SYN 洪峰)
sysctl -w net.ipv4.tcp_max_syn_backlog=8192

# 3) 开启 syncookies, SYN 队列满时也能继续建连(防 SYN flood/突发)
sysctl -w net.ipv4.tcp_syncookies=1

只调内核 somaxconn 还不够——全连接队列的实际容量是 min(listen() 的 backlog, somaxconn),如果你的应用/框架在 listen() 时传的 backlog 很小(很多默认就是 128 甚至 50),那 somaxconn 调到天上去也没用,实际队列还是被那个小 backlog 卡住。所以应用层的 backlog 也要一起调大:

// 以 Netty 服务端为例:必须显式把 SO_BACKLOG 调大
// 否则默认值很小, 内核 somaxconn 调再大也被它压住
ServerBootstrap b = new ServerBootstrap();
b.group(boss, worker)
 .channel(NioServerSocketChannel.class)
 // 关键:全连接队列 backlog, 要和内核 somaxconn 匹配
 .option(ChannelOption.SO_BACKLOG, 4096)
 // 复用 TIME_WAIT 端口, 减少端口耗尽
 .option(ChannelOption.SO_REUSEADDR, true)
 .childHandler(new MyInitializer());

// Nginx: listen 80 backlog=4096;
// Tomcat: server.tomcat.accept-count=4096 (这就是 accept 队列长度)
// Go: 标准库 net.Listen 的 backlog 取的就是 somaxconn, 调内核即可

更治本的,是第二个方向:提高应用从队列里取走并处理连接的速度。队列只是个缓冲,缓冲再大,如果消费速度长期跟不上生产速度,迟早还是会被填满。我做的几件事:把 accept 线程和业务处理线程分离(accept 只管快速接走连接、不要在 accept 线程里做任何耗时操作),保证 accept() 能尽可能快地把连接从队列里捞出来;给业务处理用独立的、足够大的线程池,避免业务慢拖累 accept;对开闸这种可预期的突发,提前扩容实例 + 预热,把瞬时洪峰分摊到更多 accept 能力上。调大队列是给突发争取缓冲时间,提高 accept 和处理速度才是让队列不被持续填满的根本。

第三件事:同一类"队列/缓冲悄悄溢出后丢弃"的坑,我后来又撞见好几个

这次踩坑之后我才意识到,"一个有限容量的队列/缓冲,在生产速度超过消费速度时被填满、然后新来的被默默丢弃"这个模式,在系统的各个角落反复出现,而且常常不报错、只丢弃,极其隐蔽:

  • 线程池的任务队列满:线程池任务队列(有界)满了之后,新任务触发拒绝策略——默认 AbortPolicy 抛异常还算好的,要是用了 DiscardPolicy 就是静默丢弃任务,和这次的连接被丢如出一辙。
  • 消息队列消费跟不上积压:生产速度长期大于消费速度,消息在 broker 里越积越多,要么撑爆存储,要么触发队列满丢弃/拒绝生产策略,消息悄悄没了。
  • UDP 接收缓冲区溢出:UDP 没有流控,收得慢、内核接收缓冲区满了,后来的数据包直接丢弃,应用根本不知道丢了几个。
  • 日志/监控采集缓冲满:异步日志、metrics 上报的本地缓冲队列满了,新日志/指标被丢,于是"出事时反而没日志"
  • 网卡 ring buffer 溢出:流量太大、网卡接收环形缓冲区满,丢包计数(rx_dropped)上涨,同样是缓冲溢出后丢弃。

它们的内核是同一个:队列/缓冲是用来吸收"生产与消费速度的瞬时不匹配"的,它的容量是有限的;当生产持续快于消费、缓冲被填满,新来的东西就会被丢弃——而很多系统在丢弃时是静默的,不报错不告警,只是默默地少了。所以光有缓冲不够,既要让缓冲够大以吸收突发,更要让消费够快以避免持续积压,还要给缓冲的"满"和"丢弃"加上监控和告警,别让它在背后悄悄丢东西。我把这套排查思路画成了一张图(见后文)。

常见的"有限缓冲" 满了之后默认行为 怎么发现它在溢出
TCP 全连接队列(accept queue) 静默丢弃完成握手的连接 netstat -s 看 overflow 计数 / ss -lnt 看 Recv-Q
TCP 半连接队列(SYN queue) 丢弃 SYN / 触发 syncookies netstat -s 看 SYNs dropped
线程池任务队列 触发拒绝策略(抛异常或静默丢) 监控队列长度 / 拒绝次数
消息队列(broker) 积压撑爆或拒绝生产/丢弃 监控积压量 lag
UDP / 网卡接收缓冲 静默丢包 netstat -su / ethtool -S 看 drop 计数

第四件事:半连接队列 vs 全连接队列——一张对照表

排查这事还逼我把 TCP 握手里那两个老搞混的队列彻底分清楚。它们处在握手的不同阶段、满了之后表现也不同:

维度 半连接队列(SYN queue) 全连接队列(accept queue)
排队的是什么连接 收到 SYN、回了 SYN-ACK、等客户端 ACK 三次握手已完成、等应用 accept()
连接状态 SYN_RECV ESTABLISHED
容量由谁决定 tcp_max_syn_backlog 等 min(listen 的 backlog, somaxconn)
满了默认行为 丢 SYN(可开 syncookies 缓解) 静默丢弃完成握手的连接
主要诱因 SYN 洪峰 / SYN flood 攻击 应用 accept 太慢 / 连接突发
怎么查 netstat -s 看 SYNs dropped ss -lnt 看 Recv-Q≈Send-Q、overflow 计数

看清这张表,这次的事故就定位得很准:我的 netstat -s 涨的是 "listen queue overflowed" 而不是 SYN 相关的丢弃,ss -lnt 看到的是 Recv-Q 顶满 Send-Q——这明确指向全连接队列(accept queue)溢出,根子在"应用 accept 速度跟不上连接涌入",而不是 SYN 洪峰。对症下药才有意义:全连接队列满,既要调大 somaxconn 和应用的 backlog,更要提高 accept 速度;如果是半连接队列满,那才该去开 syncookies、调 tcp_max_syn_backlog。分不清是哪个队列满,就可能调错参数白忙活。

第五件事:我曾经对 TCP 连接建立想当然的几个误区

这场事故把我对"连接是怎么建起来的"的一堆模糊认知照得清清楚楚:

我以为 实际上
三次握手完成应用就立刻拿到连接 先进全连接队列排队、等应用 accept() 取走
连接建不上一定会报错或 RST 队列满时内核默认静默丢弃、不报错
连不上服务端日志肯定有痕迹 没被 accept 的连接应用毫无感知、日志空白
调大内核 somaxconn 就够了 还受应用 listen 的 backlog 压制、要一起调
队列够大就不会丢连接 消费(accept)长期跟不上、再大也会被填满
SYN 队列和 accept 队列是一回事 是握手不同阶段的两个独立队列

这些误区的根子是同一个:我把"连接建立"想象成了一个应用直接、瞬间完成的动作,忽略了它底下其实是内核在用有限容量的队列做缓冲、应用只是从队列里异步取走这个事实。正因为看不见这层缓冲,我才会在它溢出、默默丢弃连接时完全摸不着头脑——既不知道连接会排队,更不知道排不下时会被静默丢掉。把一个底层有缓冲、有容量、有溢出行为的过程,当成一个无限的、瞬时的、不会失败的黑盒,是这类"看不见的丢弃"排查不下去的共同根源。

第六件事:再遇到"连不上却没日志""莫名丢东西",我现在的自检习惯

这场憋屈的事故,逼我攒下了一套排查"静默丢弃"的固定动作。先是一张连接建立全过程的流程图,帮我看清连接会卡在哪一层:

有了这张图,再碰到"连不上但服务端没日志"或任何"东西莫名其妙少了",我就按下面这张自检图走一遍,先怀疑是不是某个有限缓冲在背后默默溢出:

而把这套自检固化下来之前,我还顺手补了一个最该早做的动作——给全连接队列溢出加监控,让它别再悄无声息:

# 把全连接队列溢出做成监控指标, 定时采集这个值的增量并告警
# 一旦它在涨, 说明 accept 队列正在丢连接, 别等用户来投诉
netstat -s | awk '/listen queue of a socket overflowed/ {print "accept_overflow", $1}'

# 同时盯住每个监听端口的队列水位 (Recv-Q 逼近 Send-Q 就预警)
ss -lnt | awk 'NR>1 {print $4, "recvq="$2, "maxq="$3}'

这套习惯的精髓,是"先怀疑有限缓冲溢出、查溢出计数和队列水位、容量与消费速度两手抓、给满和丢弃加监控"它让我从"连不上就赖服务扛不住或代码有 bug",变成了"先看看是不是哪个看不见的队列被挤爆、在背后默默丢东西"——核心始终是:TCP 服务端的连接建立不是握手完就直接到应用手里,而是握手完成的连接先进内核的全连接队列(accept queue)排队、再由应用调用 accept() 逐个取走,这个队列容量有限(约为应用 listen 时传入的 backlog 与内核 net.core.somaxconn 两者的较小值,很多默认才 128),当连接涌入速度超过应用 accept 取走的速度、队列被填满时,内核对新完成握手的连接的默认行为(tcp_abort_on_overflow=0)是静默丢弃既不报错也不发 RST,导致客户端 connect 超时而服务端应用因为这些连接从未被 accept 故日志里毫无痕迹;诊断看 netstat -s 里 listen queue overflowed 计数是否上涨、ss -lnt 看监听端口 Recv-Q 是否逼近或超过 Send-Q;解决既要调大队列容量(内核 somaxconn 和应用层 backlog 如 Netty SO_BACKLOG、Tomcat accept-count、Nginx listen backlog 必须一起调大、单调内核没用因为实际容量取两者较小值)更要提高应用 accept 与处理连接的速度(accept 线程与业务线程分离、业务用独立线程池、可预期突发提前扩容预热),并给队列溢出加监控告警;更一般地,系统里一切用来吸收生产与消费速度瞬时不匹配的有限容量队列或缓冲(TCP 半连接/全连接队列、线程池任务队列、消息队列、UDP 和网卡接收缓冲、异步日志缓冲)都会在生产持续快于消费时被填满、然后把新来的静默丢弃,所以不能只把缓冲当成无限可靠的黑盒,既要让它够大以吸收突发又要让消费够快以避免持续积压、还要给它的满和丢弃加上可观测的监控告警,别让它在你看不见的地方悄悄丢东西。

我立下的几条规矩

这场"连不上却没日志"的事故,换来了我做网络服务时,刻进骨子里的几条铁律:

  1. 三次握手完成 ≠ 应用拿到连接,中间隔着内核的全连接队列。
  2. 全连接队列容量 = min(应用 backlog, somaxconn),两个都要调大。
  3. 队列满时内核默认静默丢弃连接,不报错不 RST,极其隐蔽。
  4. 连不上但服务端没日志,优先查 accept 队列是否溢出。
  5. 调大队列只是缓冲,提高 accept 和处理速度才治本。
  6. 给队列溢出(overflow 计数、Recv-Q 水位)加监控告警。
  7. 通用:一切有限缓冲都会溢出静默丢弃,容量与消费速度两手抓。

附:一段可直接照抄的 accept 队列排查与压测清单

最后留一段我自己排查"连不上却没日志"和验证队列调优时照着跑的命令清单:

# === 1. 确认是不是全连接队列在溢出(关键证据) ===
# 多跑几次看这个计数是否在涨, 涨就是 accept 队列在丢连接
watch -n1 "netstat -s | grep -i 'listen queue of a socket overflowed'"

# === 2. 看目标监听端口的队列水位 ===
# LISTEN 行: Recv-Q=当前排队等 accept 的连接数, Send-Q=队列最大容量
# Recv-Q 逼近/等于 Send-Q 就是队列要满/已满
ss -lnt 'sport = :8080'

# === 3. 看内核相关参数当前值, 不够就调大 ===
sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog net.ipv4.tcp_abort_on_overflow
sysctl -w net.core.somaxconn=4096        # 调大全连接队列硬顶
# 注意: 应用层 backlog 也要一起调(Netty SO_BACKLOG / Tomcat accept-count / Nginx listen backlog)
# 实际容量 = min(应用 backlog, somaxconn), 只调一边没用

# === 4. 改完务必重启应用让新 backlog 生效, 再压测验证 ===
# 用 ab/wrk 模拟突发并发连接, 边压边盯第 1、2 步的计数和水位
ab -n 50000 -c 2000 http://127.0.0.1:8080/health
# 压测时 overflow 计数不再涨、Recv-Q 不再顶满 Send-Q, 就说明调对了

# === 5. (临时排障)想让溢出别再静默、直接给客户端 RST 暴露问题 ===
# 把 tcp_abort_on_overflow 设 1, 队列满时回 RST 而非静默丢(仅排障用, 平时设 0)
# sysctl -w net.ipv4.tcp_abort_on_overflow=1

这段清单的顺序就是结论本身:先用 overflow 计数和 Recv-Q/Send-Q 坐实是全连接队列在溢出 → 内核 somaxconn 和应用 backlog 一起调大 → 重启应用 → 压测验证溢出不再发生。把"看不见的静默丢弃"变成"看得见的计数和水位",这事就再也不会让我对着干净的日志干瞪眼了。

写在最后

回头看,这场由"全连接队列被挤爆"引发的"连不上却没日志"事故,真正教给我的,远不止"调大 somaxconn 和 backlog"这一个技巧。它让我对"系统里那些用来吸收速度不匹配的缓冲,既是救星也是盲区",有了一次刻骨的体会。我栽跟头,是因为我把"连接建立"想象成了一个应用瞬间、直接、永不失败的动作,完全没意识到它底下是内核在用一个容量有限的队列做缓冲、而我的应用只是从这个队列里异步地、一个一个地取连接;我更没想到,当连接涌入快过我取走的速度、这个缓冲被填满时,内核会不声不响地把后来的连接丢掉——不报错、不发 RST、不留任何痕迹,以至于出了事,我对着干干净净的日志和不满的资源,完全无从下手。这让我领悟到一个关于"缓冲与可观测性"的深刻认知:缓冲(队列)是系统里极其普遍又极其重要的存在,它存在的意义,就是去吸收"生产速度和消费速度之间那种瞬时的、不可避免的不匹配"——没有它,任何一点抖动都会立刻失败;可正因为它默默地吸收了波动,我们很容易忘记它的存在、把它当成一个无限大、永远可靠的黑盒;但任何缓冲的容量都是有限的,当生产持续地快于消费,它终会被填满,而填满之后,它就必须对新来的东西做出取舍——丢弃、阻塞、还是拒绝;最危险的是静默丢弃:它一声不吭地把东西丢了,既维持了"一切正常"的假象,又让真正的故障隐身在监控和日志的盲区里;所以,对待系统里每一个缓冲,我都要做两件事:一是正视它的有限性——让它够大以吸收突发,更要让消费够快以避免它被持续填满(光扩容是治标,提速才是治本);二是消除它的盲区——给它的"水位""""丢弃"加上监控和告警,让它无论吸收了什么、丢弃了什么,都看得见这给了我一种看待"一切'莫名其妙少了点东西、却找不到原因'之事"时的直觉:每当遇到"连接连不上却没日志、数据莫名变少、请求莫名失败却查不到",我的第一反应不再是只盯着应用代码,而是去追问"这条链路上,有没有哪个有限容量的队列或缓冲,正在因为消费跟不上而被填满、然后悄悄地把东西丢掉"——先怀疑那个看不见的缓冲,去查它的溢出计数和水位;"正视缓冲的有限、消除缓冲的盲区",是做好网络服务、也是驾驭一切带缓冲的系统的关键认清 TCP 全连接队列会满、满了会静默丢连接、容量与 accept 速度要两手抓、还要给溢出加监控——这,是我用一次"一开闸就有人连不上、服务端却毫无异常"的事故,换来的、关于网络、也关于如何看见系统里那些沉默盲区的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次部署服务时,顺手把 somaxconn 和应用 backlog 一起调大、再给 accept 队列溢出加上一条告警,那我对着那段"连不上却毫无报错"的监控熬的那几个通宵,就值了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

我建表时一直顺手写 CHARSET=utf8 自以为这就是万国通吃的 UTF-8 什么字都存得下,直到有用户把昵称改成带了个笑脸 emoji,插入直接报 Incorrect string value 整条失败、宽松模式下昵称还被从 emoji 那里齐刷刷截断,排查很久才知道 MySQL 的 utf8 根本不是完整 UTF-8 而是只支持三字节的 utf8mb3 存不下四字节 emoji 的深度复盘

2026-6-3 11:08:53

技术教程

我为了把节点塞得更满提高部署密度、把 K8s 里 Pod 的内存 requests 按平时用得很少往低了配想着一个节点能多跑好几个 Pod 省机器,结果平时风平浪静一到业务高峰几个 Pod 同时上量节点内存被打爆、我的服务 Pod 莫名其妙被打上 Evicted 状态杀掉重新调度,排查很久才明白 requests 是调度器分配资源的唯一依据我把它谎报低了等于骗调度器把节点超卖了的深度复盘

2026-6-3 16:23:40

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索