← 中文资源

简体中文版本

校验连接池中的数据库连接

把 keepalive、空闲超时、生命周期上限与取用探活组合起来,让失效连接被替换而不是让用户请求失败。

阅读英文原文

概述

NAT 网关、防火墙与负载均衡器会静默回收空闲连接,两端都收不到 FIN,连接池却仍认为套接字可用。下一条查询写进黑洞后,TCP 重传可能要几分钟才报错。用户看到的是接口一直挂起,而简单重试还可能取到同一个失效连接。

步骤

  1. 在连接池配置中同时限制空闲时间、获取时间与连接总生命周期:
const pool = new pg.Pool({
  connectionString: process.env.DATABASE_URL,
  max: 10,
  idleTimeoutMillis: 30_000,
  connectionTimeoutMillis: 2_000,
  maxLifetimeSeconds: 1_800,
});
  1. 开启 TCP keepalive,让操作系统在网络层发现对端消失:
keepAlive: true,
keepAliveInitialDelayMillis: 10_000,
  1. 取用连接后执行一次 SELECT 1 探活,失败时用 client.release(err) 销毁连接再重新获取。注意不能无参释放,否则坏连接会回到池中继续被分发:
try { await client.query('SELECT 1'); return client; }
catch (err) { client.release(err); return pool.connect(); }
  1. 用 pg_terminate_backend 杀掉一个空闲后端,再发起请求验证仍返回 200。

验证

pg_stat_activity 中应看到旧后端消失、新后端建立,探活失败计数增加一次。随后持续观察 waitingCount:短暂大于 0 属于正常竞争,持续大于 0 说明连接池已饱和,需要扩池或缩短事务。若每个请求都多出一次往返,应改为仅在空闲超过阈值时探活。

注意事项

  • keepalive 的具体时序取决于操作系统与中间网络设备,必须按真实链路验证,不能只看本机。
  • 连接可能在取用之后、提交之前断开,写操作仍需幂等与有界重试。
  • maxLifetimeSeconds 是渐进替换,无法立刻修复已经装满坏连接的池。
  • 网络抖动后若连接池迅速归零,通常是没有在探活失败时销毁连接。
  • 探活是最后一道防线而不是性能优化,优先让 keepalive、空闲超时与生命周期上限覆盖绝大多数场景,并为探活单独记录延迟。