HTTP 客户端偶发 Connection reset by peer 拖垮 P99,是 keep-alive 超时错配吗,怎么定值?
该问题的正文、回答与评论。
【背景】
我们有一个网关类服务(Java 17,Spring Boot 3,16C32G),用 OkHttp 3.14 作为 HTTP 客户端,连接池 ConnectionPool(200, 5min, TimeUnit.MILLISECONDS),下游是 Nginx(1.24)反代的 20 个业务节点,Nginx 侧 keepalive_timeout 65s。网关自己出口 keep-alive 默认没显式设,依赖 OkHttp 的 5min 空闲回收。下游正常 RT 10~40ms,网关 QPS 约 3000,长尾接口 P99 预算 200ms。
【现象】
平时 P50/P99 都稳,但每隔几十分钟会有一小批(约 1%)请求报 java.net.SocketException: Connection reset by peer,对应那条请求的 RT 直接 300ms+(含一次重试),把整个接口的 P99 顶到 350~400ms。错误不是持续的,是脉冲式出现,且集中发生在"空闲连接被复用"的那一刻。CPU、GC、下游 RT 都正常,重试一次基本就成功。
【已尝试的排查】
- 怀疑是网络/网卡丢包,抓包看到 RST 包,但源端口是空闲连接,对端主动 RST;
- 把 OkHttp 连接池空闲时间从 5min 收到 30s,毛刺频率明显下降但没消失;
- 把下游 Nginx
keepalive_timeout从 65s 调到 120s 反而更频繁——说明两边空闲超时不一致,谁先关谁就 RST 对端的在途请求; - 加了自动重试后错误率降了,但 RT 毛刺还在,因为重试本身耗时间。
【期望得到的答案形态】 希望有人能说清:1) 客户端 keep-alive、服务端 keep-alive、中间 LB/代理的 idle timeout 三者到底该怎么配(谁该比谁短多少)才避免"复用即 RST";2) 这种偶发 reset 是否就该用"幂等重试 + 连接预热/健康检查"兜底,重试要不要带退避、会不会放大下游压力;3) 给一个可落地的配置对照表(客户端 / Nginx / K8s Service 或 SLB 三层的 idle timeout 推荐值)和一份避坑清单,最好带真实案例,别只说"把超时调大/调小"。