凌晨两点,监控告警刷屏,Java 服务日志里一排 java.net.SocketException: Connection reset,curl 这边报 Recv failure: Connection reset by peer。网络没断,ping 也通,接口就是偶尔失败一次。
这个报错的意思是 TCP 连接被对端直接重置了。对方发了 RST 包,连接当场作废,不是超时,也不是域名解析失败。超时是等不到回应,reset 是明确告诉你「这条连接我不要了」。
先分清是哪一段重置的
curl -v 看握手到哪一步断的:TCP 都没建起来,多半是防火墙、安全组或者对方端口没开;TLS 握手阶段断,查证书和协议版本;请求发出去才断,重点看服务端日志和中间代理。再用 tcpdump -i any host 目标IP and port 端口 抓包,看到 RST 是谁发的,责任范围就清楚了。
服务端常见的四个原因
- 进程崩溃或者发布重启,旧连接被内核回收,客户端还在往上写数据。
- 连接池里的空闲连接被 NAT、防火墙、负载均衡按超时清掉,客户端不知道,复用时撞上 RST。
- 请求体超过 nginx 的 client_max_body_size,或者 header 太大,服务端直接断开。
- 并发太高,accept 队列满了,新连接被拒。
第一种看应用日志和重启记录,第二种把连接池的空闲检测打开,比如 MySQL 的 validationQuery,HTTP 客户端设置 keep-alive 探测。第三种调大限制,第四种加机器或者限流,别硬扛。
客户端能做点什么
对幂等的 GET 请求,捕获异常后重试一两次通常能过去。POST 别随便重试,先确认服务端有没有做幂等。长连接场景把 keep-alive 时间设得比中间设备短,比如网关 60 秒超时,客户端就设 50 秒。升级 HTTP/2 之后如果报错变多,先用 curl --http1.1 对比一下。
如果只有一台客户端出问题,先查本机代理、VPN 和杀毒软件的网络防护;如果所有客户端都出问题,基本就是服务端或者中间链路。抓包看到底谁发的 RST,比盲目重启 service 有用得多。