从HikariCP连接泄漏告警到业务逻辑耗时优化实战
1. 从告警日志到问题定位那天早上刚到公司就收到运维同事发来的告警截图。日志里赫然写着Apparent connection leak detected后面跟着一堆堆栈信息。作为负责这个微服务的老司机我第一反应就是HikariCP又在报连接泄漏了。不过这次有点特殊泄漏发生在处理Excel文件上传的异步线程TyFileOpsThread-Attachworker-5上。打开日志文件仔细看发现关键线索都在报错信息里线程名明确指向SupplierProductFileOpsAppService.readDataFromExcelAttach方法事务开始的堆栈轨迹清晰可见最后跟着的业务处理耗时显示整整271秒这里有个细节很有意思。HikariCP的泄漏检测机制是通过leak-detection-threshold参数工作的我们配置的是6000毫秒6秒。当连接被占用超过这个阈值就会触发警告。但实际业务处理耗时271秒远超这个阈值难怪会报警。2. 深入分析连接泄漏的根源2.1 事务注解的隐形陷阱查看代码发现这个Excel处理方法被Transactional注解包裹着。这意味着方法开始时会获取数据库连接整个方法执行期间都会持有这个连接方法结束时才会释放连接问题就出在这里。当方法执行时间过长特别是处理大数据量Excel时连接被占用的时间就会超出HikariCP的泄漏检测阈值。虽然实际上连接最终会被正确释放但长时间的占用会被误判为泄漏。2.2 业务逻辑的耗时分析通过添加详细日志我们发现耗时主要分布在三个环节Excel解析POI库处理大型Excel文件本身就比较吃资源数据校验对每行数据都要进行复杂的业务规则校验状态更新最后还要更新文件处理状态特别是当供应商上传的Excel包含上万行数据时这个链条式的处理过程就会变得异常缓慢。我见过最夸张的一个文件处理了将近5分钟数据库连接自然会被长时间占用。3. 双管齐下的解决方案3.1 业务逻辑优化实战针对发现的性能瓶颈我们实施了以下优化措施分批次处理Excel数据// 原代码一次性读取所有行 ListRow rows sheet.getRows(); // 优化后分批读取 int batchSize 500; for(int i0; itotalRows; ibatchSize){ ListRow batch sheet.getRows(i, Math.min(batchSize, totalRows-i)); processBatch(batch); }异步化耗时操作// 将校验逻辑放入线程池 CompletableFutureVoid validationFuture CompletableFuture.runAsync(() - { validateData(batch); }, validationExecutor); // 等待所有校验完成 validationFuture.get();缓存重复查询结果// 使用Guava Cache缓存常用数据 LoadingCacheLong, ProductInfo productCache CacheBuilder.newBuilder() .maximumSize(1000) .build(new ProductInfoLoader());3.2 HikariCP参数调优指南在优化业务代码的同时我们也调整了连接池配置参数名原值新值说明leak-detection-threshold6000ms60000ms适当放宽泄漏检测阈值maximum-pool-size2030增加最大连接数connection-timeout60000ms120000ms延长连接等待时间特别注意leak-detection-threshold不是越大越好。设置过长会失去泄漏检测的意义建议根据业务实际情况调整。4. 长事务处理的进阶技巧4.1 事务拆分策略对于这种长时间运行的任务我推荐使用小事务策略将大事务拆分为多个小事务每个批次处理完成后立即提交使用状态表记录处理进度Transactional(propagation Propagation.REQUIRES_NEW) public void processBatch(ListRow batch) { // 处理逻辑... }4.2 连接持有时间监控我们开发了一个简单的监控工具用于跟踪连接持有时间public class ConnectionHoldTimeMonitor { private static final ThreadLocalLong startTime new ThreadLocal(); public static void start() { startTime.set(System.currentTimeMillis()); } public static void end() { long duration System.currentTimeMillis() - startTime.get(); if(duration 5000) { // 超过5秒警告 log.warn(Long connection hold detected: {}ms, duration); } } }5. 典型场景的避坑指南在实际项目中我遇到过不少由长事务引发的连接问题这里分享几个典型案例案例一报表生成服务问题月度报表生成耗时20分钟导致连接池耗尽解决改用文件存储中间状态分阶段生成案例二数据迁移工具问题百万级数据迁移时频繁报连接泄漏解决采用分页查询批量插入每1000条提交一次案例三第三方API集成问题等待外部API响应时连接被占用解决设置合理超时将调用移出事务边界这些案例都说明了一个道理事务范围应该与业务操作的实际需求相匹配。不是所有操作都需要放在事务里特别是那些耗时较长的IO操作。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2466062.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!