一、什么是druid连接池
Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为。
1.1、竞品分析
功能类别 | 功能 | Druid | HikariCP | DBCP | Tomcat-jdbc | C3P0 |
性能 | PSCache | 是 | 否 | 是 | 是 | 是 |
LRU | 是 | 否 | 是 | 是 | 是 | |
SLB负载均衡支持 | 是 | 否 | 否 | 否 | 否 | |
稳定性 | ExceptionSorter | 是 | 否 | 否 | 否 | 否 |
扩展 | 扩展 | Filter | JdbcIntercepter | |||
监控 | 监控方式 | jmx/log/http | jmx/metrics | jmx | jmx | jmx |
支持SQL级监控 | 是 | 否 | 否 | 否 | 否 | |
Spring/Web关联监控 | 是 | 否 | 否 | 否 | 否 | |
诊断支持 | LogFilter | 否 | 否 | 否 | 否 | |
连接泄露诊断 | logAbandoned | 否 | 否 | 否 | 否 | |
安全 | SQL防注入 | 是 | 无 | 无 | 无 | 无 |
支持配置加密 | 是 | 否 | 否 | 否 | 否 |
1.2、几个概念
JDBC:Java Database Connectivity, Java链接数据库和执行SQL 语句的API
JNDI:Java Naming and Directory Interface ,Java命名和目录接口,JNDI是指使用是指使用源连接数据库的方式,可以简单理解成所有Data Source的集合
DBCP:Database Connection Pool, 数据库连接池。数据库连接池就是链接数据库的进程/线程的集合
Data Source: 数据源。就是将IP、数据库、用户名、密码封装起来对外只提供一个JNDI名称,在应用中只需要调用JNDI就可以连接数据库,而不需要写入用户名和密码
以上几个概念组成了目前主流数据库的交互流程
1、应用程序通过JNDI服务查找对应的Data Source
2、通过Data Source向DBCP发起数据库连接
3、DBCP根据自身策略分配资源利用JDBC连接数据库,建立Connect
4、通过Connect与数据库进行交互
二、源码分析
2.1、配置分析
2.2、getConnection总流程
2.3、源码分析
最核心的类的DruidDataResource,DruidDataResource里面有三个核心的内部类CreateConnectTask,CreateConnectionThread,DestroyConnecttionThread。从getConnection获取连接作为入口,我们可以快速看到
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
// 次数会初始化CreateConnectThread和DestoryConnectionThread守护线程
init();
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
return getConnectionDirect(maxWaitMillis);
}
}
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
DruidConnectionHolder holder;
...
for (boolean createDirect = false;;) {
...
// 最大连接数大于0
if (maxWait > 0) {
// 从连接池队列中获取一个连接
holder = pollLast(nanos);
} else {
holder = takeLast();
}
...
}
...
}
private DruidConnectionHolder pollLast(long nanos) throws InterruptedException, SQLException {
long estimate = nanos;
for (;;) {
// 连接池内连接大于0
if (poolingCount == 0) {
emptySignal(); // send signal to CreateThread create connection
if (failFast && isFailContinuous()) {
throw new DataSourceNotAvailableException(createError);
}
if (estimate <= 0) {
waitNanosLocal.set(nanos - estimate);
return null;
}
notEmptyWaitThreadCount++;
if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) {
notEmptyWaitThreadPeak = notEmptyWaitThreadCount;
}
try {
long startEstimate = estimate;
estimate = notEmpty.awaitNanos(estimate); // signal by
// recycle or
// creator
notEmptyWaitCount++;
notEmptyWaitNanos += (startEstimate - estimate);
if (!enable) {
connectErrorCountUpdater.incrementAndGet(this);
if (disableException != null) {
throw disableException;
}
throw new DataSourceDisableException();
}
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to non-interrupted thread
notEmptySignalCount++;
throw ie;
} finally {
notEmptyWaitThreadCount--;
}
if (poolingCount == 0) {
if (estimate > 0) {
continue;
}
waitNanosLocal.set(nanos - estimate);
return null;
}
}
decrementPoolingCount();
// 获取连接池中connections的最后一个连接
DruidConnectionHolder last = connections[poolingCount];
// 代表connections该位置连接已经被使用了
connections[poolingCount] = null;
long waitNanos = nanos - estimate;
last.setLastNotEmptyWaitNanos(waitNanos);
return last;
}
}
获取连接的时候发现poolCount=0,这个时候会唤醒createThread去创建Connection。创建线程是之前提到的CreateConnectionThread来负责的;先看下这个线程的初始化
private final CountDownLatch initedLatch = new CountDownLatch(2);
// 创建创建连接的守护线程
createAndStartCreatorThread();
// 创建销毁连接的守护线程
createAndStartDestroyThread();
// 等待以上两个守护线程初始化完成后再继续
initedLatch.await();
创建创建连接的守护线程createAndStartCreatorThread,
protected void createAndStartCreatorThread() {
if (createScheduler == null) {
String threadName = "Druid-ConnectionPool-Create-" + System.identityHashCode(this);
// 创建创建连接的线程
createConnectionThread = new CreateConnectionThread(threadName);
createConnectionThread.start();
return;
}
initedLatch.countDown();
}
public class CreateConnectionThread extends Thread {
public CreateConnectionThread(String name){
super(name);
// 设置为守护线程
this.setDaemon(true);
}
public void run() {
initedLatch.countDown();
long lastDiscardCount = 0;
int errorCount = 0;
// 死循环创建连接
for (;;) {
// addLast
try {
lock.lockInterruptibly();
} catch (InterruptedException e2) {
break;
}
long discardCount = DruidDataSource.this.discardCount;
boolean discardChanged = discardCount - lastDiscardCount > 0;
lastDiscardCount = discardCount;
try {
boolean emptyWait = true;
if (createError != null
&& poolingCount == 0
&& !discardChanged) {
emptyWait = false;
}
if (emptyWait
&& asyncInit && createCount < initialSize) {
emptyWait = false;
}
if (emptyWait) {
// 必须存在线程等待,才创建连接
if (poolingCount >= notEmptyWaitThreadCount //
&& (!(keepAlive && activeCount + poolingCount < minIdle))
&& !isFailContinuous()
) {
// 初始化的时候一直在这里等待
empty.await();
}
// 防止创建超过maxActive数量的连接
if (activeCount + poolingCount >= maxActive) {
empty.await();
continue;
}
}
} catch (InterruptedException e) {
lastCreateError = e;
lastErrorTimeMillis = System.currentTimeMillis();
if ((!closing) && (!closed)) {
LOG.error("create connection Thread Interrupted, url: " + jdbcUrl, e);
}
break;
} finally {
lock.unlock();
}
PhysicalConnectionInfo connection = null;
try {
// 创建物理连接
connection = createPhysicalConnection();
} catch (SQLException e) {
LOG.error("create connection SQLException, url: " + jdbcUrl + ", errorCode " + e.getErrorCode()
+ ", state " + e.getSQLState(), e);
errorCount++;
if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) {
// fail over retry attempts
setFailContinuous(true);
if (failFast) {
lock.lock();
try {
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
if (breakAfterAcquireFailure) {
break;
}
try {
Thread.sleep(timeBetweenConnectErrorMillis);
} catch (InterruptedException interruptEx) {
break;
}
}
} catch (RuntimeException e) {
LOG.error("create connection RuntimeException", e);
setFailContinuous(true);
continue;
} catch (Error e) {
LOG.error("create connection Error", e);
setFailContinuous(true);
break;
}
if (connection == null) {
continue;
}
boolean result = put(connection);
if (!result) {
JdbcUtils.close(connection.getPhysicalConnection());
LOG.info("put physical connection to pool failed.");
}
errorCount = 0; // reset errorCount
if (closing || closed) {
break;
}
}
}
}
创建销毁连接的守护线程DestroyConnectionThread
protected void createAndStartDestroyThread() {
destroyTask = new DestroyTask();
if (destroyScheduler != null) {
long period = timeBetweenEvictionRunsMillis;
if (period <= 0) {
period = 1000;
}
// 目测是使用netty的时间轮算法进行定时执行
destroySchedulerFuture = destroyScheduler.scheduleAtFixedRate(destroyTask, period, period,
TimeUnit.MILLISECONDS);
initedLatch.countDown();
return;
}
String threadName = "Druid-ConnectionPool-Destroy-" + System.identityHashCode(this);
destroyConnectionThread = new DestroyConnectionThread(threadName);
destroyConnectionThread.start();
}
public class DestroyConnectionThread extends Thread {
public DestroyConnectionThread(String name){
super(name);
// 守护线程
this.setDaemon(true);
}
public void run() {
initedLatch.countDown();
for (;;) {
// 从前面开始删除
try {
if (closed || closing) {
break;
}
if (timeBetweenEvictionRunsMillis > 0) {
Thread.sleep(timeBetweenEvictionRunsMillis);
} else {
Thread.sleep(1000); //
}
if (Thread.interrupted()) {
break;
}
destroyTask.run();
} catch (InterruptedException e) {
break;
}
}
}
}
public class DestroyTask implements Runnable {
public DestroyTask() {
}
@Override
public void run() {
shrink(true, keepAlive);
if (isRemoveAbandoned()) {
removeAbandoned();
}
}
}