最近在线上环境发现Druid连接池出现了InterruptedException异常,其实涉及到锁的使用,这篇文章我们尝试分析下连接池和可重入锁的实现,来对这个问题进行一个比较全面的分析。

异常堆栈内容如下:

	java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.alibaba.druid.pool.DruidDataSource.recycle(DruidDataSource.java:1281)
at com.alibaba.druid.pool.DruidPooledConnection.recycle(DruidPooledConnection.java:297)
at com.alibaba.druid.pool.DruidPooledConnection.close(DruidPooledConnection.java:247)
at com.kugou.fanxing.revenue.persist.DefaultNsqMessageDao.insertOrUpdateByMessageIdAndAppId(DefaultNsqMessageDao.java:98)
at com.kugou.fanxing.revenue.persist.DefaultNsqMessageDao.insertOrUpdateByMessageIdAndAppId(DefaultNsqMessageDao.java:31)
at com.kugou.fanxing.revenue.persist.MessagePersistCommand.run(MessagePersistCommand.java:40)
at com.kugou.fanxing.revenue.persist.MessagePersistCommand.run(MessagePersistCommand.java:15)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)

这里我们主要提出几个问题。

根据这几个问题,我们再去分析下连接池回收连接的逻辑。

我们的业务场景是通过Hystrix去把DB的更新操作封装起来。

graph TD
subgraph Hystrix
A(获取连接)-->B(更新DB)
B-->C(释放连接)
end

注:Druid版本为1.0.6,Hystrix版本为1.5.12

连接池回收

我们看下DruidPooledConnection的连接池回收逻辑

public void recycle() throws SQLException {
        ...
        if (!this.abandoned) {
            DruidAbstractDataSource dataSource = holder.getDataSource();
            //回收逻辑
            dataSource.recycle(this);
        }
        ...
    }

最终逻辑会到DruidDataSource的recycle方法

protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
    //加锁
    lock.lockInterruptibly();
            try {
                activeCount--;
                closeCount++;

                putLast(holder, lastActiveTimeMillis);
                recycleCount++;
            } finally {
                lock.unlock();
            }
    } catch (Throwable e) {
            holder.clearStatementCache();

            if (!holder.isDiscard()) {
                this.discardConnection(physicalConnection);
                holder.setDiscard(true);
            }

            LOG.error("recyle error", e);
            recycleErrorCount.incrementAndGet();
        }
        
}

lock是ReentrantLock类的一个对象。如果当前线程已经被中断,这里会抛异常。但是异常处理的其实只会简单打一个回收异常。

我们再看看Hystrix的处理逻辑。

HystrixCommand.java

public Future<R> queue() {
    ...
if (!isExecutionComplete() && interruptOnFutureCancel.get()) {
                    final Thread t = executionThread.get();
                    if (t != null && !t.equals(Thread.currentThread())) {
                        //把当前线程设置为中断
                        t.interrupt();
                    }
                }
    ...
}

如果执行失败会把当前线程设置为中断。

结论

当Hystrix执行超时或者执行失败,会导致Druid连接池回收失败

参考文献

https://www.cnblogs.com/lingyejun/p/9064114.html