连接池失效——高并发下的隐形杀手
连接池失效——高并发下的隐形杀手系统挂了现象用户打开页面一直转圈。5分钟后页面报错。错误日志org.apache.tomcat.jdbc.pool.PoolExhaustedException: [http-nio-8080-exec-72] Timeout: Pool empty. Unable to fetch a connection in 30 seconds, none available[size:100; busy:100; idle:0; lastwait:30000]诊断连接池满了。100个连接全部被占用没有空闲连接可用。这不是第一次也不是最后一次。连接池是什么连接池是数据库连接的蓄水池应用线程 → 从连接池取连接 → 执行SQL → 归还连接到池 ↓ ┌──────────────┐ │ 连接池 │ │ ┌──┐┌──┐┌──┐│ │ │c1││c2││c3││ ← 预先创建好的数据库连接 │ └──┘└──┘└──┘│ └──────────────┘正常情况线程用完连接就归还连接池循环利用。故障情况线程拿了连接不归还连接池逐渐枯竭。故障案例案例1数据库重启连接池假死处理因数据库重启而失效的中间件连接池场景Oracle 数据库例行重启后应用无法访问数据库。排查数据库已正常启动应用服务器已重启但应用还是报连接超时根因连接池中的连接是数据库重启前创建的。数据库重启后这些连接已经失效TCP 连接断开。但连接池不知道连接已失效继续把坏连接分配给应用。解决方案!-- Tomcat JDBC Pool 配置 --Resourcenamejdbc/datasourceauthContainertypejavax.sql.DataSourcefactoryorg.apache.tomcat.jdbc.pool.DataSourceFactorymaxActive100maxIdle30minIdle10initialSize10!--关键配置连接有效性检查--validationQuerySELECT 1 FROM DUAL validationQueryTimeout5 testOnBorrowtrue!-- 取连接时检查 --testWhileIdletrue!-- 空闲时检查 --timeBetweenEvictionRunsMillis30000 minEvictableIdleTimeMillis60000!-- 数据库重启后自动重建连接 --removeAbandonedtrue removeAbandonedTimeout60 logAbandonedtrue /教训连接池必须配置连接有效性检查否则数据库重启就是一场灾难。案例2连接池泄露——代码忘了归还异地平台连接池故障分析及处理注释掉连接放入容器中 异地平台连接池泄露问题解决与测试现象系统运行一段时间后变慢最终崩溃。排查# 1. 查看连接数netstat-an|grep1521|grepESTABLISHED|wc-l# 结果200# 2. 查看应用连接池状态JMX# busy: 100, idle: 0根因代码中获取连接后在异常分支没有归还。// 错误代码连接泄露publicvoidprocessData(){Connectionconnnull;try{conndataSource.getConnection();// 业务处理...// 这里抛出异常thrownewBusinessException(处理失败);}catch(Exceptione){// 只记录日志没有归还连接log.error(处理失败,e);// 缺少conn.close();}}修复// 正确代码try-with-resources 自动归还publicvoidprocessData(){try(ConnectionconndataSource.getConnection()){// 业务处理...}catch(Exceptione){log.error(处理失败,e);}// 自动归还连接}排查技巧开启removeAbandoned和logAbandoned定位泄露点。removeAbandonedtrue removeAbandonedTimeout60 logAbandonedtrue启用后日志会打印WARNING: Connection has been abandoned. PooledConnection[x.x.x.x:1521] StackTrace: com.xxx.dao.DataProcessor.processData(DataProcessor.java:45) com.xxx.service.DataService.handle(DataService.java:23) ...案例3一体化系统连接池跟踪一体化跟踪连接池泄露 一体化数据连接泄露跟踪现象一体化系统运行几天后连接池耗尽。排查过程开启连接池监控记录每天的连接使用情况对比正常时段和异常时段// 连接池监控代码ComponentpublicclassConnectionPoolMonitor{Scheduled(fixedRate60000)// 每分钟记录一次publicvoidmonitor(){DataSourcedsgetDataSource();if(dsinstanceoforg.apache.tomcat.jdbc.pool.DataSource){org.apache.tomcat.jdbc.pool.DataSourcetomcatDS(org.apache.tomcat.jdbc.pool.DataSource)ds;PoolStatsstatsnewPoolStats();stats.setActive(tomcatDS.getActive());stats.setIdle(tomcatDS.getIdle());stats.setSize(tomcatDS.getSize());stats.setWaitCount(tomcatDS.getWaitCount());stats.setTimestamp(newDate());log.info(连接池状态: active{}, idle{}, size{}, wait{},stats.getActive(),stats.getIdle(),stats.getSize(),stats.getWaitCount());// 如果活跃连接数超过阈值告警if(stats.getActive()tomcatDS.getMaxActive()*0.8){alertService.sendAlert(连接池告警,stats.toString());}}}}发现某个定时任务在周末数据量大时执行慢连接占用时间长。修复// 优化增加超时控制Scheduled(cron0 0 2 * * ?)publicvoidbatchProcess(){// 1. 限制单次处理数量intbatchSize1000;// 2. 分批处理每批之间释放连接for(inti0;itotalCount;ibatchSize){try{processBatch(i,batchSize);}catch(Exceptione){log.error(批次处理失败,e);// 失败后继续下一批而不是一直占用连接}// 每批完成后短暂休眠让其他线程有机会获取连接Thread.sleep(100);}}案例4MyBatis 连接池管理问题一体化问题处理连接占完 一体化系统性能分析推测是mybatis连接池管理问题现象连接池满了但代码看起来没问题都用了 try-with-resources。根因项目用的 MyBatis 自带连接池POOLEDpoolMaximumActiveConnections只配了10个高峰期远远不够。同时嵌套事务的传播行为配置不当导致连接占用时间过长。排查过程——先判断是池太小还是泄漏原因特征排查方式连接池太小高峰期定时耗尽重启后正常负载下来后也正常调大连接池观察连接泄漏越来越严重即使低峰也耗尽检查代码是否忘关连接临时把poolMaximumActiveConnections从 10 调到 30 观察一周依然耗尽——确认是连接泄漏。代码泄漏点定位// 嫌疑一手动获取Connection后没有在finally中关闭Connectionconnsession.getConnection();PreparedStatementpsconn.prepareStatement(sql);ResultSetrsps.executeQuery();// rs、ps、conn 都没有关闭// 嫌疑二事务未正常提交/回滚DBUtil.BeginTrans(false);// 如果中间抛异常EndTrans没调用连接不归还// 嫌疑三大数据导出ResultSet流式读取未关闭ResultSetresultDBUtil.getBigResult(session,mapperClass,methodName,params);// 导出完没关resultStatement和Connection都占着修复方案导出逻辑的 finally 块中确保关闭 ResultSet 和 Statement封装 try-with-resources 安全获取连接的方法BeginTrans/EndTrans 增加30秒超时保护超时写入 warn 日志MyBatis POOLED vs 生产级连接池功能MyBatis POOLEDDruid/HikariCP连接泄漏检测无有强制回收超时连接慢SQL记录无有连接池监控无有JMX/SQL面板动态调整不支持支持长期方案换用 Druid/HikariCPremoveAbandoned参数可自动回收泄漏连接。解决调大连接池、调整事务传播行为、缩短事务范围。同时在导出逻辑 finally 中确保关闭 ResultSetBeginTrans/EndTrans 加超时告警封装 try-with-resources 安全方法。案例5MongoDB 连接池mongodb连接池修改改为连接集群使用配置文件场景MongoDB 从单机升级到集群连接池配置需要调整。// MongoDB 连接池配置MongoClientOptionsoptionsMongoClientOptions.builder().connectionsPerHost(100)// 每个主机的最大连接数.minConnectionsPerHost(10)// 最小连接数.threadsAllowedToBlockForConnectionMultiplier(5).connectTimeout(5000)// 连接超时.socketTimeout(30000)// Socket超时.maxWaitTime(10000)// 最大等待时间.build();MongoClientclientnewMongoClient(Arrays.asList(newServerAddress(192.168.1.1,27017),newServerAddress(192.168.1.2,27017),newServerAddress(192.168.1.3,27017)),options);连接池配置大全Tomcat JDBC PoolResourcenamejdbc/datasourcetypejavax.sql.DataSourcefactoryorg.apache.tomcat.jdbc.pool.DataSourceFactorymaxActive100!--最大活跃连接数--maxIdle30!-- 最大空闲连接数 --minIdle10!-- 最小空闲连接数 --initialSize10!-- 初始连接数 --maxWait30000!-- 最大等待时间(ms) --validationQuerySELECT 1 FROM DUAL validationQueryTimeout5 testOnBorrowtrue testOnReturnfalse testWhileIdletrue timeBetweenEvictionRunsMillis30000 minEvictableIdleTimeMillis60000 removeAbandonedtrue removeAbandonedTimeout60 logAbandonedtrue jdbcInterceptorsConnectionState;StatementFinalizer;SlowQueryReport(threshold1000) /Druid PoolbeaniddataSourceclasscom.alibaba.druid.pool.DruidDataSourcepropertynameurlvaluejdbc:oracle:thin:localhost:1521:orcl/propertynameusernamevalueuser/propertynamepasswordvaluepass/propertynameinitialSizevalue10/propertynameminIdlevalue10/propertynamemaxActivevalue100/propertynamemaxWaitvalue30000/propertynamevalidationQueryvalueSELECT 1 FROM DUAL/propertynametestOnBorrowvaluetrue/propertynametestWhileIdlevaluetrue/propertynametimeBetweenEvictionRunsMillisvalue60000/propertynameremoveAbandonedvaluetrue/propertynameremoveAbandonedTimeoutvalue60/propertynamelogAbandonedvaluetrue/!-- 慢SQL监控 --propertynamefiltersvaluestat,wall,log4j//beanHikariCP推荐spring:datasource:hikari:maximum-pool-size:50minimum-idle:10idle-timeout:300000max-lifetime:600000connection-timeout:30000connection-test-query:SELECT 1 FROM DUALleak-detection-threshold:60000诊断工具1. 查看数据库连接数-- Oracle 查看当前连接SELECTusername,count(*)FROMv$sessionWHEREusernameISNOTNULLGROUPBYusername;-- 查看具体连接信息SELECTsid,serial#, username, machine, program, statusFROMv$sessionWHEREusernameAPP_USERORDERBYstatus;2. 查看应用连接池JMX# 通过 JMX 查看 Tomcat 连接池jconsole localhost:1099# → MBeans → Catalina → DataSource → connectionPool3. 查看线程堆栈# 找出哪些线程在等待连接jstackpid|grep-A20POOL EXHAUSTEDjstackpid|grep-A20borrowConnection4. 数据库级排查-- Oracle 查看锁SELECTobject_name,session_id,oracle_username,os_user_name,locked_modeFROMv$locked_object lo,dba_objectsdoWHERElo.object_iddo.object_id;-- 查看长时间运行的SQLSELECTsid,serial#, username,ROUND(elapsed_seconds/60,2)asminutes,sql_textFROMv$sessions,v$sqlqWHEREs.sql_idq.sql_idANDs.statusACTIVEANDs.usernameISNOTNULLORDERBYelapsed_secondsDESC;常见原因总结原因表现解决方案代码未归还连接活跃连接数持续上升try-with-resources事务过长连接占用时间长缩小事务范围慢SQL连接执行慢排队优化SQL、加索引数据库重启连接全部失效testOnBorrow并发突增连接不够用增大连接池、限流连接泄露活跃连接慢慢增加removeAbandoned死锁连接互相等待优化锁顺序网络抖动连接假死连接超时检测经验教训1. 连接池不是越大越好maxActive100 满了 → 改成 200 还是满了 → 改成 500 数据库扛不住了连接池越大数据库压力越大。根本问题是为什么连接占用时间这么长2. 一定要配置连接有效性检查testOnBorrowtrue validationQuerySELECT 1 FROM DUAL没有这个配置数据库重启一次系统就要重启一次。3. 一定要开启泄露检测removeAbandonedtrue logAbandonedtrue生产环境必须开启否则连接泄露只有到系统崩溃时才发现。4. 监控比预防更重要连接池问题很难在测试环境复现并发不够。生产环境必须监控活跃连接数等待连接数连接获取时间连接关闭时间5. 慢SQL是连接池的头号杀手一条慢 SQL 执行 10 秒如果有 100 个连接只能同时处理 10 个请求剩下的 90 个排队。先优化慢 SQL再调整连接池。最后的话连接池问题是隐形杀手——平时不出问题一出就是大问题。日志里多次出现的连接池泄露、“连接占完”、“连接池故障分析”每次都是系统快挂了才发现。事后分析原因都很简单代码少了个close()事务范围太大了SQL 太慢了。但查出来往往要花半天。最有效的方案是代码层面try-with-resourcesJava 7 的自动资源管理配置层面testOnBorrow removeAbandoned监控层面连接池指标实时监控超过 80% 就告警
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2617486.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!