一个线程中断引发Bug的“爆肝”排查经历( 三 )


/** * 直接清空上一批次的历史数据 */private void clearHistoryData(BigDataFileLogicParam param, String fileName){Collection reportFileParseServiceList = reportBaseService.getReportFileParseServiceList();for(ReportFileParseService reportFileParseService : reportFileParseServiceList) {if(reportFileParseService.supportReportFileAndInsertHandle(param.getBizType())) {try {reportFileParseService.clearHistoryData(param,fileName);} catch (Exception e) {OpLogUtil.onError("清空历史数据","异常",e.getMessage());Thread.currentThread().interrupt();}break;}}}复制代码上面的代码 , 在清理历史数据的公共方法中 , 如果实现类清理出现了异常就会调用 Thread.currentThread().interrupt(); 导致线程中断标志位被设为true , 进而在后面的解析入库逻辑中 调用 DruidDataSource.getConnectionInternal() 获取连接时抛出中断异常 。
为了验证这个推断 , 我将这块代码改成下面这样 , 在清空数据实现类抛出异常时 , 不再调用 Thread.currentThread().interrupt(); 改成仅打印错误日志 , 重新部署后 , 只要搜索日志关键字 “清空历史数据 , 异常” , 并且插入数据正常落库就能验证这个问题了 。
/** * 直接清空上一批次的历史数据 */private void clearHistoryData(BigDataFileLogicParam param, String fileName){Collection reportFileParseServiceList = reportBaseService.getReportFileParseServiceList();for(ReportFileParseService reportFileParseService : reportFileParseServiceList) {if(reportFileParseService.supportReportFileAndInsertHandle(param.getBizType())) {try {reportFileParseService.clearHistoryData(param,fileName);} catch (Exception e) {OpLogUtil.onError("清空历史数据","异常",e.getMessage());// Thread.currentThread().interrupt();}break;}}}复制代码改动后重新部署 , 再次调用接口 , 果然收到了 “清空历史数据 , 异常”的日志 , 并且之前的获取数据库连接异常:
一个线程中断引发Bug的“爆肝”排查经历文章插图
终于不再出现了 。
寻找上游问题之前这段代码是已经上线过了 , 其他有好几个实现类都走的这套逻辑 , 为什么就我这个接口出现问题了呢? 这里根据代码逻辑继续追踪 , 肯定是我的清理历史数据的实现类出现了问题 , 而我这次确实改动了清空历史数据的实现代码 , 就是如果数据已经回传 , 则直接报错:
@Overridepublic void clearHistoryData(BigDataFileLogicParam param,String fileName) throws Exception {if (!preCheckFilePass(fileName, 8)) {OpLogUtil.logOpStep("文件预校验", "异常", param.getBizSeq(),param.getDate(), param.getFileId());throw new Exception("data receive has exists!");}}复制代码检查数据库中对应日期的数据是否已经回传 , 果然 , 对应的测试环境的数据库已经有很多脏数据存在 , 终于前因后果都对上了;
此时已经凌晨1点了 , 我赶紧将问题已经排查解决的结果告诉组内老板 , 因为他回家路上还给我打了好几个电话指导思路 , 肯定也很担心这个问题(我可不是想表现什么的 , 认真脸)
一个线程中断引发Bug的“爆肝”排查经历文章插图
小结至此 , 耗时5小时的获取不到连接异常终于搞定了 , 我来做下小结:
「对事方面的小结:」
问题综述:

  1. 测试环境存在人为插入导致数据日期冲突的脏数据;
  2. 在清理历史数据的实现方法中增加了抛出异常操作 , 导致外层公共代码执行了线程中断操作;
  3. 调用清理历史数据的公共方法在异常处理中调用了 线程的 interrupt() 方法;
  4. 调用了线程 interrupt() 方法 , 导致解析插入数据 , 获取数据库连接时抛出中断异常;
解决方案:
  1. 将接口流程中所有调用 Thread.currentThread().interrupt(); 方法改为抛出异常 , 让调用方感知到错误;
「需要明确注意的是:」
Thread.currentThread().interrupt(); 方法不能使线程立刻中断退出 , 而只是设置了线程的中断属性为true , 在线程调用可以响应中断的方法时 , 才会将异常抛出 , 将控制权交给捕获中断异常的代码块 。比如当调用 线程的 sleep() 方法 , wait()方法 ,lock.lockInterruptibly() 方法时 , 都是必须强制捕获中断异常的 ,这些情况才会响应线程的中断状态 。
这也是为什么在清理数据的时候调用了 interrupt 方法 , 但是线程还一直运行到了解析入库的流程中 , 在获取数据库连接的代码中有调用 lock.lockInterruptibly() 这才抛出了中断异常 , 因为从清理数据到解析落库这个过程中没有调用任何可以相应中断的方法 , 所以这中断的状态一直没有发挥作用 , 相当于埋了一颗定时炸弹 , 不知道他啥时候会爆炸 , 导致出现离奇错误 。