连接池与线程池
池化的本质:复用昂贵资源,避免反复创建。面试主线:连接池解决什么、线程池参数怎么定、两类任务怎么配。
为什么需要池
创建数据库连接、TCP 连接、线程都是昂贵操作(握手、认证、内存分配)。池化后:
- 连接创建一次,反复借用
- 控制资源上限,防止打爆下游(数据库连接数、文件描述符)
- 等待队列管理过载(排队而非拒绝)
数据库连接池
| 参数 | 含义 | 调优方向 |
|---|---|---|
| 最大连接数 | 池的上限 | 别超过数据库上限(PG 默认 100 连接) |
| 最小空闲 | 常驻连接 | 预热,避免冷启动 |
| 连接超时 | 借不到等多久 | 过长拖垮请求,过短误杀峰值 |
| 空闲回收 | 空闲多久回收 | 配合数据库空闲超时 |
| 泄漏检测 | 借出不还的检测 | 定位连接泄漏 |
面试常见公式:连接数 = 核数 × 2 + 磁盘数(HikariCP 官方建议的上限推导,实际按压测调)。
关键坑:连接池打满的典型症状是 RT 飙升(请求排队等连接)。排查:池活跃数、等待时间、是否有连接泄漏(事务未提交、会话未关闭)。
HTTP 连接池
- keep-alive 复用连接:避免每个请求新建 TCP(三次握手 + TLS)
- 参数:最大连接数、每路由连接数、空闲超时、获取超时
- 典型场景:服务间调用(内部 API)、调用第三方
- 与数据库连接池同理:池满即排队,设置获取超时快速失败
线程池
Python 的 ThreadPoolExecutor 参数(Java 的 ThreadPoolExecutor 同思路):
| 参数 | 作用 |
|---|---|
| 核心线程数 | 常驻线程 |
| 最大线程数 | 峰值上限 |
| 队列容量 | 排队缓冲 |
| 拒绝策略 | 队列满怎么办(丢弃/抛错/调用者执行) |
核心公式:任务类型决定线程数:
| 任务类型 | 线程数 | 原因 |
|---|---|---|
| CPU 密集 | 核数 + 1 | 线程多了一分钱性能不涨,反而上下文切换 |
| IO 密集 | 核数 × 2 或更高 | 线程大部分时间在等 IO,多线程填充等待 |
判断任务类型:看线程在等(IO、网络、锁)还是算(CPU 占用高)。
池的通用设计原则
- 设上限,设超时:池必须有界,等待必须有超时,否则故障时雪崩
- 监控池状态:活跃数、等待队列、拒绝数,告警阈值
- 先压测再调参:公式给起点,压测验证(见性能指标篇)
- 分层池化:应用线程池 → 连接池 → 下游连接池,每层都限流,避免层层放大
面试追问
- 为什么用连接池? 建连成本高(握手认证),复用 + 限流。池化是性能与稳定性的双重收益
- 连接池打满怎么办? 查泄漏(事务未提交)、看池参数、压测调上限。连接数是下游资源,别盲目调大
- CPU 密集和 IO 密集怎么配线程? CPU 密集核数 + 1,IO 密集核数 × 2 以上。核心是线程在等还是在算
- 拒绝策略有哪些? 抛异常、丢弃、丢弃最旧、调用者线程执行。生产要告警,别静默丢弃
- 池和无池的区别? 无池每请求建连,高并发下连接数失控、延迟高。池化后资源可控、延迟稳定