小Cの已经记不起来的博客

性能优化手记:连接池 排查全过程

事情是这样的,前几天晚上监控突然开始报警,某个服务的接口平均响应时间从 80ms 一路飙到 2 秒多,偶尔还夹杂几个 500。第一反应是数据库扛不住了?登录服务器看了一眼,CPU 正常,内存正常,慢查询日志里也没几个新面孔。

又过了几分钟,错误日志里开始冒 Cannot get a connection, pool error Timeout waiting for idle object。这下有方向了,连接池拿不到连接,要么是池子太小被瞬间打满,要么是连接被借出去之后一直没还。我倾向于后者,因为流量并没有明显上涨。

先确认池子的状态。我们用的是 Druid,直接把内置的监控页拉出来看。

curl -s http://127.0.0.1:8080/druid/datasource.json | jq

结果很经典,PoolingCount 掉到了 0,ActiveCount 顶在 maxActive 上,WaitThreadCount 一直往上涨。数据库本身 max_connections 还有富余,所以问题确实出在应用这边,借出去的连接回不来。

接下来就是找谁没还。这种问题靠猜没用,得拿数据说话。Druid 有个 removeAbandoned 的开关,但线上不能随便开,它会把超时连接强行关掉,有风险。我换了个思路,直接在测试环境把池子的借还日志打开。

druid.logAbandoned=true
druid.removeAbandoned=true
druid.removeAbandonedTimeout=60

跑了一轮压测,日志里果然跳出来一堆堆栈,指向同一个 DAO 方法。点进去看,代码长这样:

Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
// ... 处理结果

rs 和 ps 关了,conn 没关。这段代码在一个很少走到异常分支里,平时流量小的时候看不出问题,一旦某个条件触发,连接就被悄悄吞掉一个。跑了一晚上,池子就被漏干了。

问题定位之后修起来就快了,改成 try-with-resources,连接、语句、结果集一起托管给 JVM。

try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
    // ...
}

不过事情没完。修完代码我又回头看了一眼池子的配置,发现几个参数也不太对劲,之前估计是复制粘贴留下的。

druid.initialSize=2
druid.minIdle=2
druid.maxActive=20
druid.maxWait=3000

minIdle 只有 2,maxActive 20,流量稍微抖一下就开始频繁创建和销毁连接,maxWait 3 秒也偏短,数据库偶尔打个嗝就直接把请求打回 500。我把 minIdle 提到 10,maxActive 提到 50,maxWait 放宽到 10 秒,顺便把 validationQuery 和 testWhileIdle 补上,避免拿到已经死掉的连接。

改完之后重新压测,ActiveCount 稳定在个位数,WaitThreadCount 长期是 0,接口 P99 从 2 秒多回到了 120ms 左右。

复盘的时候又想到一个点,那个漏连接的方法其实在异常分支里,如果当时直接把 maxWait 调大、池子调大,问题会被掩盖更久,说不定哪天流量翻倍才爆出来,到时候排查成本更高。所以遇到连接池打满,先别急着加参数,先弄清楚到底是池子小还是连接漏。Druid 的 removeAbandoned 虽然不适合长期开在线上,但在测试环境跑一轮,基本上一抓一个准。

最后贴一下最终版本的配置,留个记录:

druid.initialSize=5
druid.minIdle=10
druid.maxActive=50
druid.maxWait=10000
druid.validationQuery=SELECT 1
druid.testWhileIdle=true
druid.timeBetweenEvictionRunsMillis=60000
druid.minEvictableIdleTimeMillis=300000

连接池这东西平时不起眼,真出问题的时候能把整个服务拖垮,该开的监控还是得开,该看的日志还是得看,别等报警了才想起来翻代码。

评论

还没有评论。

发表评论

提交后评论将经过自动审核,审核通过后公开展示。

未在播放