
一次凌晨的 PoolExhausted让我重写了 Tomcat 连接池监控与调优方法【免费下载链接】tomcatApache Tomcat项目地址: https://gitcode.com/gh_mirrors/tom/tomcat凌晨 2:47值班群被报警刷屏下单接口 P99 从 300ms 爬到 3.2s日志里开始出现org.apache.tomcat.jdbc.pool.PoolExhaustedException。重启没用扩容也没用——直到我们把 Tomcat 连接池监控接进告警才发现活跃连接数死死卡在 20 纹丝不动后面排队的线程越攒越多。说白了池子早就打满了只是我们一直看不见。这篇文章讲的不是连接池 API 大全而是三个具体问题怎么把池子的状态实时摊开来看tomcat-jdbc JMX监控、连接池耗尽时如何一步步定位根因连接池耗尽诊断、参数到底该怎么调Tomcat连接池调优。一、连接池的黑箱里到底发生了什么先建立一个直觉。把数据库连接想成银行柜台的窗口一个连接 一个窗口maxActive 窗口总数getNumActive() 正在办业务的窗口数getNumIdle() 空着的窗口数getWaitCount() 已经取了号、坐在椅子上等叫号的人数泄露的连接 拿了号却赖在座位上不走的人这套指标全部定义在 ConnectionPoolMBean 接口中。但问题在于你在context.xml里写下的 maxActive、minIdle 只是开业方案开业之后窗口每天被借走几次、有没有人占座不走、每次办业务花多久——这些池子自己不会主动上报。没有观测手段时你只能靠用户投诉来猜池子的状态。这次事故的教训就是参数配得好 ≠ 池子运行得好连接池监控必须成为常态化能力而不是出事后的临时补丁。真正值得盯的指标只有五个各自异常时你看到什么活跃连接数长期贴住 maxActive 不动说明池子饱和容量和占用时长至少有一个出了问题空闲连接数持续为 0 表示一点缓冲都没有任何毛刺都会变成排队等待数 waitCount本次事故的主角持续为正就是池子被打满的直接证据借出/归还计数差值借出数减归还数理论上应长期稳定在活跃连接数附近像图书馆用外借总量倒查失窃的书泄露回收计数被强制回收的连接数涨一个就说明线上有一个真实的泄露点二、 把连接池的状态摊开来看JMX 相当于给 Java 进程装了一块仪表盘进程把内部状态注册成 MBean外部工具通过端口读数值、调方法。连接池的仪表盘节点就是org.apache.tomcat.jdbc.pool:name池名。开启 Tomcat JMX 远程访问的完整步骤修改启动脚本追加 JVM 参数然后jconsole连上host:1099在 MBeans 标签下逐层展开找到池子节点CATALINA_OPTS$CATALINA_OPTS \ -Dcom.sun.management.jmxremote \ -Dcom.sun.management.jmxremote.port1099 \ -Dcom.sun.management.jmxremote.rmi.port1099 \ -Dcom.sun.management.jmxremote.authenticatetrue \ -Dcom.sun.management.jmxremote.password.file\$CATALINA_BASE/conf/jmxremote.password代码方式拉取指标适合写自定义巡检脚本或内部监控面板MBeanServer mbs ManagementFactory.getPlatformMBeanServer(); ObjectName on new ObjectName( org.apache.tomcat.jdbc.pool:typeDataSource,context/orders,namejdbc/MyDB); int active (int) mbs.getAttribute(on, NumActive); int waiters (int) mbs.getAttribute(on, WaitCount);生产环境的完整链路一般长这样指标最终落到 Grafana 上按 15s 粒度出曲线三、 连接池耗尽时的三步诊断流程拿到曲线之后别急着调参。我们沉淀下来的诊断顺序是三步每一步都有明确的判断标准。第一步看 waitCount确认够不够用。active 贴住 maxActive 且 waitCount 持续为正说明池子已打满waitCount 间歇性尖峰但几秒内回落说明只是峰值流量打穿容量。判断标准waitCount 连续 5 分钟大于 0基本可以锁定容量不足或有人不还连接二选一。第二步借出/归还对比区分不够用还是不还。拉出 borrowedCount 与 returnedCount 的增长曲线。两者同步上涨且 active 长期高位说明连接正常还回、只是每笔业务占用时间变长——这时池子参数没问题锅在 SQL 或事务粒度上。反过来借出涨得快、归还跟不上差值持续走高的那段就是没还回来的连接数指向泄露。本次事故里两条曲线斜率一致排除了泄露把嫌疑转给了单条连接占用时间。第三步removeAbandonedCount 慢查询日志锁定最后一环。前者在涨直接确认泄露给池配上logAbandonedtrue再调 MBean 的checkAbandoned()强制触发一轮检测日志会打印每个被回收连接借出时的完整堆栈行号直接指向问题代码——比任何猜测都快。后者用于排除泄露之后在池的jdbcInterceptors里挂SlowQueryReportJmx超过阈值的 SQL 会发 JMX 通知MBean 上还能按语句查出最慢耗时Resource namejdbc/MyDB authContainer typejakarta.sql.DataSource urljdbc:mysql://db.internal:3306/orders driverClassNamecom.mysql.cj.jdbc.Driver maxActive20 minIdle2 removeAbandonedtrue removeAbandonedTimeout120 logAbandonedtrue jdbcInterceptorsorg.apache.tomcat.j2ee.pool.interceptor.SlowQueryReportJmx(threshold800)/回到凌晨那次事故第 1 步定位到池子打满第 2 步排除泄露第 3 步慢查询报告里全是SELECT ... WHERE order_no ?单条查询每条 40~60ms、单请求循环执行 30 次——当天灰度上线的订单导出接口写了个 N1还漏关了一个 Statement。修复后曲线立刻回落。四、 参数怎么调跟着现象走现象先看再调active 长期贴住 maxActivewaitCount 间歇为正业务是否真变慢maxActive上调如 20→30waitCount 为正借出/归还基本同步SQL 耗时先治慢查询别动容量removeAbandonedCount 在涨借出堆栈removeAbandonedTimeout调小如 120→60createdCount / releasedCount 高频抖动连接寿命minIdle上调、timeBetweenEvictionRunsMillis拉长调优顺序比参数本身更重要三步诊断没过之前不要碰容量参数。泄露场景下扩池只是把爆掉的时间从一小时推迟到三小时慢查询场景下扩池只是把瓶颈从连接池推到数据库连接数和锁竞争上tomcat 连接池性能分析的终点永远应该落在 SQL 和事务边界。本次的完整动作与效果N1 循环查询改成一次 IN 查询maxActive20→30minIdle2→10SlowQueryReportJmx阈值 800ms上线前调 MBean 的resetStats()清零统计、压测建立基线。对比数据waitCount 从持续 5~15 回落到 0active 稳定在 8~12 而不是贴死 30接口 P99 从 3.2s 降到 210ms数据库侧的连接数也终于不再一路涨。五、可以贴进运维手册的检查清单确认 JMX 远程访问已开启且配了authenticate与密码文件密码文件权限 400端口只对监控网段开放确认池 MBean 已注册jconsole 能看到org.apache.tomcat.jdbc.pool节点指标可拉取配置告警waitCount 连续 5 分钟 0 定级 P1removeAbandonedCount 环比增长 0 同样 P1生产环境保持removeAbandonedtrue与logAbandonedtrue堆栈信息是泄露定位的捷径SlowQueryReportJmx阈值按业务 P95 响应时间的 1/3 设定而不是照抄默认值每次发布后跑一遍压测先resetStats()再采集建立本次版本的指标基线把active 贴满 waitCount 爬升写进事故响应手册要求 10 分钟内给出容量/SQL/泄露三选一的初步结论监控的价值不在曲线本身而在下次 waitCount 开始爬升时你已经有办法在 10 分钟内说清楚问题是容量、SQL 还是代码。【免费下载链接】tomcatApache Tomcat项目地址: https://gitcode.com/gh_mirrors/tom/tomcat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考