长连接空闲一段时间后被固定秒数断开,复用连接时偶发 reset 或 EOF。
HTTP 客户端、数据库连接池或 gRPC 连接在空闲后复用时失败,报 connection reset by peer、EOF、server closed the connection unexpectedly;断开时间往往固定(如 60 秒、350 秒、5 分钟)。新连接通常正常。
先确认断开时间是否固定,以及各层 keepalive/idle 配置:
ss -tno | grep -E 'keepalive|timer'
ss -tno state established '( dport = :3306 or dport = :5432 or dport = :6379 )'
sysctl net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes
代理与应用层:
nginx -T | grep -E 'keepalive( |_timeout|_time)|proxy_read_timeout|proxy_send_timeout'
grep -R 'IdleTimeout\|IdleConnTimeout' .
数据库侧对照(idle_session_timeout 需 PostgreSQL 14+):
mysql -e "SHOW VARIABLES LIKE 'wait_timeout'; SHOW VARIABLES LIKE 'interactive_timeout';"
psql -c "SHOW idle_session_timeout;" -c "SHOW tcp_keepalives_idle;"
redis-cli CONFIG GET timeout
云 LB/NAT 的空闲超时也要核对:
aws elbv2 describe-load-balancer-attributes --load-balancer-arn <arn> --query 'Attributes[?Key==`idle_timeout.timeout_seconds`]'
conntrack -L -p tcp --dport 443 | head
易误判:Nginx 的 keepalive_timeout 不是 TCP keepalive;upstream 块里的 keepalive 是连接池大小,keepalive_time 才是空闲时长。TCP keepalive 也不能替代应用心跳去维持 LB/NAT 的空闲计时 —— 探测包常常不被转发或不被计为活跃流量。固定 60 秒断开更像 ALB/Nginx,固定 350 秒更像 NLB/NAT;数据库报 “server closed the connection” 通常是 DB 或代理先关闭,而不是客户端。
keepAliveTimeout,复用时撞上正在关闭的连接。maxLifetime 大于 DB wait_timeout 或代理超时,拿到失效连接。too_many_pings/GOAWAY。IdleConnTimeout/maxIdleTime 设为 LB idle 的 80% 左右,并对幂等请求处理 stale connection 重试。maxLifetime 比 DB wait_timeout/代理超时短 30–60 秒,开启连接有效性检测。tcp_keepalive_time(如 300 秒)、tcp_keepalive_intvl、tcp_keepalive_probes,应用可设 per-socket TCP_KEEPIDLE。EnforcementPolicy.MinTime。tcp_tw_recycle 或缩短 tcp_fin_timeout 来“解决” keepalive 问题。keepalive_timeout 与 TCP keepalive 是同一件事吗?maxLifetime 应如何与 wait_timeout 配合?keepalive_timeout 是 HTTP 连接空闲超时,TCP keepalive 是内核探测参数。maxLifetime 应短于 wait_timeout 或代理超时(通常再留 30–60 秒余量),并配合连接有效性检测。