连接池
复用数据库和 HTTP 连接而非每请求新开,减延迟和资源用。
为什么
开数据库连接贵——涉 TCP 握手、TLS 协商、认证和会话建立,共 50-100ms。无池每请求付此成本,负载下数据库连接耗尽(Postgres 默认最大 100)开始拒请求。连接池保一组暖连接按请求分发,故每请求成本 0ms(连接已开)且总连接由池大小有界。这是处理 100 RPS(无池、每请求连接)和 10000 RPS(池、复用连接)服务的差。每个数据库客户端和 HTTP 客户端支持池;唯一问题是你否正确配。
何时使用
任何处理超 1 请求的服务中数据库和 HTTP 连接始终用池。基于数据库最大连接和服务实例数配池大小。
如何做
1. 在数据库客户端启池
- Node/pg: new Pool({ max: 20, idleTimeoutMillis: 30000 })
- Python/psycopg: psycopg2.pool.SimpleConnectionPool(minconn=5, maxconn=20)
- Java/HikariCP: HikariConfig.setMaximumPoolSize(20)
- Go/database/sql: db.SetMaxOpenConns(20); db.SetMaxIdleConns(5)
2. 正确设池大小
- 公式: pool_size = (db_max_connections - 10) / num_service_instances
- 例: DB 允 100 连接、4 服务实例 → 每实例池大小 22
- 太小: 请求等连接排队(延迟尖峰)
- 太大: DB 连接耗尽(其他服务饿)
3. 配空闲和生命超时
- idleTimeoutMillis: 关空闲超 30 秒连接(释 DB 资源)
- connectionTimeoutMillis: 5 秒内无连接即快失败(别永排队)
- maxLifetime: 每 30 分钟回收连接(防 DB 重启后陈旧连接)
4. 每进程单共享池,非每请求或每模块
- 启动时建一次池,跨所有 handler 共享
- 每模块池建 N × M 连接耗尽 DB
- 用依赖注入或单例共享池
5. HTTP 客户端用持久连接 agent
- Node: new http.Agent({ keepAlive: true, maxSockets: 50 })
- Python: httpx.AsyncClient()(内置连接池)
- Go: http.Client 带自定义 Transport(MaxIdleConnsPerHost)
- 复用客户端实例——不每请求新建示例
// 数据库连接池(Node + pg)
import { Pool } from "pg";
// 启动时建一个共享池——不每请求建
export const db = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // 池中最大连接
idleTimeoutMillis: 30000, // 空闲 30 秒后关连接
connectionTimeoutMillis: 5000, // 5 秒内无连接快失败
maxLifetimeSeconds: 1800, // 每 30 分钟回收连接
});
// 用池——检出和释放自动
async function getUser(id: string) {
// pool.query 检出连接、跑查询、归还
const { rows } = await db.query("SELECT * FROM users WHERE id = $1", [id]);
return rows[0];
}
// 带连接池的 HTTP 客户端(Node)
import http from "node:http";
import https from "node:https";
const agent = new https.Agent({
keepAlive: true, // 跨请求复用连接
maxSockets: 50, // 每主机最大并发连接
maxFreeSockets: 10, // 保 10 空闲 socket 供复用
timeout: 30000, // socket 空闲超时
});
// 跨所有 HTTP 调用共享 agent
export const httpClient = new https.Agent({ keepAlive: true });
// fetch(url, { agent: httpClient })——复用连接
// 池大小公式:
// DB 最大连接: 100
// 服务实例: 4
// 每实例池: (100 - 10) / 4 = 22
// 留 10 连接供管理员、迁移、监控常见问题
- ⚠每请求新建数据库连接(每 handler 中 new Client())——每请求付 50-100ms 连接建立,负载下数据库连接耗尽拒请求。
- ⚠池大小太高(每实例 100、10 实例、DB 最大 100)——实例共请求 1000 连接,DB 允 100,900 请求排队或失败。
- ⚠每模块或每请求建独立池而非一个共享池——10 模块各池 20 建 200 连接,耗尽数据库允 100。
- ⚠不设空闲或生命超时——连接永空闲,DB 终关它们,池分发陈旧连接以连接重置错误失败。
最佳实践
- ✓启动时每进程建一个共享池注入所有 handler——单池有界总连接确保跨整个应用复用。
- ✓用公式 (db_max_connections - 10) / num_instances 设池大小——这留管理连接余量确保所有实例共在 DB 限下。
- ✓设空闲超时(30 秒)和最大生命(30 分钟)使陈旧连接回收——这防池分发数据库静默关闭的连接。
- ✓HTTP 客户端用 keep-alive agent 带共享 maxSockets 设置——这消重复调同 API 的 TCP 握手,每调用减延迟 50-100ms。