连接池生命周期
This content is not available in your language yet.
Druid 连接池的核心不是“保存一组连接”,而是把租借状态、空闲队列、创建任务和失效回收组织成一个并发状态机。
先给答案:连接池用复用换建连成本,但把生命周期责任集中起来
Section titled “先给答案:连接池用复用换建连成本,但把生命周期责任集中起来”借出路径要从可用队列拿到连接,并检查连接是否已关闭、超时或失效;归还路径要把连接重新标记为空闲,并重置事务、自动提交等状态。后台任务负责补充连接、检测空闲和淘汰老连接,业务线程不应承担全部维护工作。
池太小会让应用线程排队,池太大则会让数据库线程、锁和缓冲区过载。连接泄漏、坏连接复用和归还状态污染是三类不同问题,应分别从借出记录、健康检测和代理 reset 排查。
new connection | vidle deque <---- recycle/close ---- active proxy | | +---- takeLast / validate --------+ | create task / shrink / destroyDruidDataSource#getConnection:DruidDataSource.java:1339。- 超时版本
getConnection(long)::1343。 - Filter chain 分支:
:1348-1358,启用过滤器时先由链包装。 getConnectionDirect::1373,执行真实借出。takeLast::2221,从空闲连接结构取出 holder。- 借出后执行有效性和池状态校验:
:1373-1400附近。
application close() -> DruidPooledConnection#close -> recycle() -> DruidDataSource.recycle -> idle queue / destroyDruidPooledConnection#close:DruidPooledConnection.java:236。DruidPooledConnection#recycle::329。DruidDataSource#recycle:DruidDataSource.java:1901。- 池关闭或连接失效时,回收路径会转入销毁而非重新入队。
CreateConnectionTask:DruidDataSource.java:2567,把连接创建从借出线程中拆出。- 原始连接建立:
DruidAbstractDataSource.java:1694-1696。 shrink:DruidDataSource.java:3068,按空闲时间、最小空闲数等条件清理。lock和Condition:DruidAbstractDataSource.java:242-244,用于等待和唤醒。
为什么把创建拆成任务
Section titled “为什么把创建拆成任务”替代方案:借连接线程发现池为空时同步创建。
为什么不行:数据库连接建立包含网络、认证和初始化 SQL,突发流量会让大量业务线程同时卡在创建上。
证据:CreateConnectionTask 独立存在于 DruidDataSource.java:2567,借出路径只负责取用和触发补充。
| 场景 | 现象 | 原因 | 规避 |
|---|---|---|---|
| 连接泄漏 | active 数持续上升 | 代理未 close | 使用 try-with-resources |
| 校验过重 | 借连接延迟上升 | 每次借出都执行 validation query | 按网络质量配置校验策略 |
| 池关闭中借出 | 抛异常或拿到失效连接 | 生命周期状态变化 | 应用关闭前先停止流量 |
| 创建风暴 | 数据库连接数瞬时打满 | minIdle/初始化并发过高 | 限制创建速率和最大连接数 |
连接池要同时建模“资源状态”和“线程等待状态”。队列、条件变量、后台创建任务与失败回收应当作为一个整体审查,不能只看 borrow 方法。
面试锚点
- Druid 借还连接的关键状态转换是什么?
- 为什么关闭池连接通常是 recycle 而不是 close?
- 连接校验失败后如何避免把坏连接重新放回池?