strace -f -e trace=network 发现大量 connect/reset 的业务排查

11次阅读

大量 connect 后立即返回 ECONNREFUSED 或 RST,主因是业务代码反复连接未监听的地址/端口、目标服务崩溃或绑定错误(如仅监听 127.0.0.1 却连 localhost 触发 ipv6 解析失败)。

strace -f -e trace=network 发现大量 connect/reset 的业务排查

为什么 strace -f -e trace=network 看到大量 connect 后立刻 connect(…) = -1 ECONNREFUSEDreset

这通常不是网络本身卡顿,而是业务代码在反复尝试连接一个**根本没在监听的地址/端口**,或者目标服务已崩溃、未启动、监听绑定错误(如只绑 127.0.0.1 却连 localhost 触发 IPv6 解析失败)。strace 抓到的是系统调用层面行为,它不区分“重试逻辑”和“配置错误”,只忠实地记录每次 connect 调用及其返回值。

  • 检查目标服务是否真实运行:ss -tlnp | grep :端口号,确认 StateLISTENPID 匹配预期进程
  • 注意地址解析问题:如果配置里写的是 localhost,而服务只监听 127.0.0.1,glibc 可能先尝试 IPv6 的 ::1,失败后才回退 IPv4——strace 会把两次都记下来
  • 确认防火墙没静默丢包:iptables -L -n -v | grep DROP(或 nft list ruleset),ECONNREFUSED 是内核明确拒绝,而超时(ETIMEDOUT)才更可能是防火墙拦截或路由不通

如何快速定位是哪个线程/进程在疯狂重连

strace -f 输出默认不带时间戳和线程 ID,海量日志里找源头很吃力。必须加参数增强可读性:

  • -tt 打印微秒级时间戳,便于对齐业务日志
  • -T 显示每次系统调用耗时,connect 耗时极短(几微秒)基本就是立即拒绝,而非等待
  • -p PID 替代 -f(如果已知主进程 PID),避免跟踪无关子进程;必要时用 ps -T -p PID 查线程 ID,再针对性 strace -p TID
  • grep -E "(connect|ECONNREFUSED|EHOSTUNREACH|reset)" 过滤,配合 awk '{print $1,$2,$NF}' 提取时间、PID、错误码,快速聚类

connect 返回 reset(即 RST 包)的真实含义

这里 reset 不是 strace 自己说的,而是指 connect 返回 -1errno == ECONNRESET,或抓包看到 TCP 层收到 RST。这说明对方 TCP 主动发了复位,常见于:

  • 目标端口有进程在监听,但该进程在 accept() 前就崩溃了(比如 fork 子进程失败、资源耗尽),内核会代为 RST 后续连接
  • 服务启用了连接限制(如 nginxlimit_conn),超出阈值的连接被直接 RST
  • 某些代理或中间件(如 HAproxy、Envoy)配置了健康检查失败后的“快速拒绝”策略,不等超时直接 RST
  • 注意:RST 和 FIN 不同,它表示异常终止,不能靠客户端重试解决,必须查对端状态

strace 更高效的替代排查路径

持续 strace 开销大、日志爆炸,适合快速定性;真要根因分析,优先组合轻量工具

  • ss -tni 查看连接状态分布:SYN-SENT 多说明客户端发了请求但没响应;TIME-WaiT 爆满可能意味着短连接风暴
  • tcpdump -i any port 端口号 -w conn.pcap 抓几秒包,然后 tshark -r conn.pcap -Y "tcp.flags.reset==1" 精准定位 RST 发送方
  • 检查应用层配置:比如 java 应用的 spring.redis.host 是否误配,pythonrequests.get("http://wrong-host") 是否写死错误域名
  • 若使用连接池(如 HikariCP、urllib3),确认 maxIdle/minIdle 设置是否合理,空闲连接被服务端断开后,池子没及时剔除失效连接,导致下次取出就 RST

真正麻烦的从来不是看到多少次 connect,而是那些没打日志的重试——它们藏在框架底层,只在 strace 里裸奔。所以第一反应不该是调优,而是确认“这个连接本该成功”。

text=ZqhQzanResources