Hikari连接池——java.lang.Exception: Apparent connection leak detected

问题分析

首先,先看报错:

java.lang.Exception: Apparent connection leak detected
	at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128) ~[HikariCP-3.4.5.jar:?]
	at com.skyline.MyTest.getConnection(MyTest.java:36)
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_261]

Apparent connection leak detected,网上很多解释都说是连接泄露,但是这个报错就等于连接泄漏了吗?先说答案:并不是!!!
首先,这个报错是从ProxyLeakTask中抛出来的,源码不多,先贴出来

class ProxyLeakTask implements Runnable
{
   private static final Logger LOGGER = LoggerFactory.getLogger(ProxyLeakTask.class);
   static final ProxyLeakTask NO_LEAK;
   //调度器
   private ScheduledFuture<?> scheduledFuture;
   private String connectionName;
   //异常对象
   private Exception exception;
   //线程名
   private String threadName; 
   //是否泄露
   private boolean isLeaked;

   static
   {
      //创建一个不检测连接泄漏的task
      NO_LEAK = new ProxyLeakTask() {
         @Override
         void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) {}

         @Override
         public void run() {}

         @Override
         public void cancel() {}
      };
   }
   
   //构造器,需要传入连接池中的连接对象
   ProxyLeakTask(final PoolEntry poolEntry)
   {
      //※重点来了,这时创建一个异常对象,当ProxyLeakTask被new出来的时候,就是连接被业务代码申请的时候
      this.exception = new Exception("Apparent connection leak detected");
      this.threadName = Thread.currentThread().getName();
      this.connectionName = poolEntry.connection.toString();
   }

   private ProxyLeakTask()
   {
   }

   //※很重要,开始调度到倒计时开始
   void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold)
   {
      //拿到这个返回值是为了将来可以cancel这个调度
      scheduledFuture = executorService.schedule(this, leakDetectionThreshold, TimeUnit.MILLISECONDS);
   }

   /** {@inheritDoc} */
   @Override
   public void run()
   {
      //当代码执行到这里时,说明上面的调度没有被cancel,
      //那么Hikari就认为之前申请的连接可能已经泄露了,
      //这个run方法是由executorService回调的
      isLeaked = true;

      final StackTraceElement[] stackTrace = exception.getStackTrace(); 
      final StackTraceElement[] trace = new StackTraceElement[stackTrace.length - 5];
      System.arraycopy(stackTrace, 5, trace, 0, trace.length);

      exception.setStackTrace(trace);
      LOGGER.warn("Connection leak detection triggered for {} on thread {}, stack trace follows", connectionName, threadName, exception);
   }

   //取消调度,这个方法会在连接被释放时回调
   void cancel()
   {
      //先把这个调度任务取消
      scheduledFuture.cancel(false);
      if (isLeaked) {
         //很遗憾,如果之前调度任务已经走过了,那么就再打个日志说明一下
         //之前那个被认为已经泄露了的连接会到池子里了
         LOGGER.info("Previously reported leaked connection {} on thread {} was returned to the pool (unleaked)", connectionName, threadName);
      }
   }
}

接下来我们看下ProxyLeakTask是什么时候被创建的,首先,我们来到这个方法:com.zaxxer.hikari.pool.HikariPool#getConnection(long)这个是从Hikari中获取连接的方法,大家都知道,但凡你要操作数据库,必然先取连接。我们重点关注一下这个方法 poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
在这里插入图片描述
一层一层的看,我们先看这个leakTaskFactory.schedule方法:

ProxyLeakTask schedule(final PoolEntry poolEntry)
{
	//先判断leakDetectionThreshold是不是等于0,
	//如果等于0,返回最一开始那个不检查连接泄露的Task,
	//如果不等于0,就通过scheduleNewTask创建一下
    return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry);
 }

重头戏scheduleNewTask来了

private ProxyLeakTask scheduleNewTask(PoolEntry poolEntry) {
	//创建一个task
    ProxyLeakTask task = new ProxyLeakTask(poolEntry);
    //开始调度,※注意,这里就开始倒计时了!!!!
    task.schedule(executorService, leakDetectionThreshold);

    return task;
}

那么poolEntry.createProxyConnection返回的是个什么对象呢?答案是com.zaxxer.hikari.pool.ProxyConnection这里肯定是代理了。我们重点关注一下连接释放的方法吧:
在这里插入图片描述
看到没有,当连接被释放时,会回调leakTask的cancel方法。

总结

  1. leakDetectionThreshold如果等于0,那么Hikari就不会做连接泄露的检查
  2. leakDetectionThreshold的时间=从获取调用getConnection()开始到调用connection.close()的时间,注意,如果从数据库中获取真正的连接的耗时也是计算在内的!
  3. Apparent connection leak detected并不等于连接泄露,我理解只能是猜测可能出现了连接泄露,如果日志中可以找到成对的Previously reported leaked connection {} on thread {} was returned to the pool (unleaked)那么可以确定的是,连接并没有泄露,只是连接的使用时长>leakDetectionThreshold了,虽然不是连接泄露,但是可能是执行慢的SQL,也是需要进一步跟踪排查的
  4. 这个连接泄露的检查机制真的很厉害,从来没想过调度任务可以这么玩,确实有意思,打开了新世界的大门。

引申一下,leakDetectionThreshold的配置项在spring.datasource.hikari.leak-detection-threshold单位是毫秒

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐