http请求挂起本质是“无声等待”,需通过tcpdump/tshark在客户端、代理、后端三处抓包,结合mtr/tcping定位链路抖动,检查apache keepalivetimeout/proxytimeout配置、accept队列溢出及线程阻塞,确认每段连接按预期交付。

请求挂起在复杂网络拓扑中往往不是“失败”,而是“无声等待”——没有错误码、没有超时日志、连接卡在 ESTABLISHED 状态,直到客户端或代理层主动断开。这种问题最难排查,因为表象是“没动静”,根源却可能横跨网络路径、中间设备策略、协议协商或后端服务状态。
抓包确认挂起发生在哪一层
先别猜,直接用 tcpdump 或 tshark 在关键节点捕获流量,重点看三次握手是否完成、是否有应用层数据发出、是否有 ACK 丢失或重复重传:
- 在客户端侧抓包:若 SYN 发出后无响应 → 网络层或防火墙拦截;若三次握手完成但无 HTTP 请求 → 客户端代码阻塞(如未设
context.WithTimeout) - 在负载均衡(如 Apache
mod_proxy)侧抓包:若收到请求但无发往后端的 SYN → 代理配置限流或连接池耗尽;若发出 SYN 但无后端 ACK → 后端端口未监听或被丢包 - 在后端服务侧抓包:若收到 SYN+ACK 但无后续数据 → 应用层 accept 队列满(
netstat -s | grep -i "listen overflows"),或进程卡在系统调用(如strace -p $PID)
检查 Apache 代理层的 KeepAlive 与超时协同
Apache 作为高可用代理时,KeepAliveTimeout 和后端实际响应行为不匹配,极易导致连接挂起。例如前端设了 30 秒超时,后端因数据库锁死 45 秒才返回,Apache 会一直等,不主动中断。
-
KeepAliveTimeout必须 ≤ 后端服务的连接空闲超时(如 Spring Boot 的server.connection-timeout),否则 Apache 维持连接而上游已关闭,下次复用时触发 RST -
ProxyTimeout要显式设置(如ProxyTimeout 15),它控制 Apache 等待后端响应的最大时间,否则默认继承Timeout(通常 300 秒),远超业务容忍阈值 - 禁用
ProxyBadHeader Ignore时,若某跳中间设备插入非法头字段(如重复Content-Length),Apache 可能静默丢弃整个请求而不报错,表现为“挂起”
用 mtr + tcping 定位链路抖动点
单纯 ping 不足以反映真实业务链路状态。HTTP 请求挂起,大概率是某段路径 TCP 握手成功但数据包持续重传或被限速。
- 运行
mtr -c 100 -n <backend_ip></backend_ip>:重点关注丢包率突增且持续 ≥2 跳的位置,尤其是云厂商边界网关或跨机房专线入口 - 对目标端口做持续探测:
tcping -x 20 -t 5 <backend_ip><port></port></backend_ip>,观察是否偶发超时(>5s)——这比 ICMP 更贴近真实请求行为 - 若发现某跳延迟从 10ms 跃升至 300ms 且伴随丢包,基本可判定该节点或其上行链路存在 QoS 限速或 buffer 溢出,需联系网络侧调整策略
验证后端服务的 accept 队列与线程阻塞
挂起常发生在后端,但日志无记录。常见原因是内核 accept 队列满(ListenOverflows)或业务线程全部阻塞在同步 I/O 上。
- 执行
ss -lnt查看监听端口的Recv-Q值:若长期 > 0,说明新连接无法入队,客户端 SYN_ACK 后收不到 ACK,表现为“连接慢”或“挂起” - 检查
/proc/sys/net/core/somaxconn和应用 listen() 的backlog参数是否匹配(如 Java 的ServerSocket(int port, int backlog)) - 用
jstack $PID(Java)或gdb -p $PID -ex 'thread apply all bt' -ex quit(Go/C)看所有线程是否卡在read、write或数据库驱动调用上
真正麻烦的挂起,往往藏在“看起来都通”的缝隙里:一跳设备悄悄限速、一个中间件静默丢包、一段代码忘了设 context 超时。排查时得放弃“找错误”的思维,转向“确认每一段都按预期交付”。











