数据库连接池之Druid源码解析
一、Druid的使用
1.1、Springboot项目集成druid
1.1.1、配置maven
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.15</version
</dependency>
1.1.2、添加数据源相关配置
1 spring: 2 datasource:
druid: 3 url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDataTimeBehavior=convertToNull&useSSL=false 4 username: root 5 password: root 6 type: com.alibaba.druid.pool.DruidDataSource 7 initialSize: 5 8 maxInactive: 10 9 minIdle: 5 10 timeBetweenEvictionRunsMillis: 5000 11 minEvictableIdleTimeMillis: 10000 12 filters: stat,wall 13 testOnBorrow: false
1.1.3、定义DruidConfig配置文件
1 package com.lucky.test.config; 2 3 import com.alibaba.druid.pool.DruidDataSource; 4 import org.springframework.boot.context.properties.ConfigurationProperties; 5 import org.springframework.context.annotation.Configuration; 6 7 import javax.sql.DataSource; 8 9 /** 10 * @Auther: Lucky 11 * @Date: 2020/12/14 下午8:16 12 * @Desc: 13 */ 14 @Configuration 15 public class DruidConfig { 16 17 @ConfigurationProperties(prefix = "spring.datasource") 18 public DataSource druidDataSource(){ 19 return new DruidDataSource(); 20 } 21 }
定义了DruidDataSource数据源的bean之后,项目中使用的就是数据源就是DruidDataSource了
1.2、Druid数据源的配置
配置项 | 案例值 | 描述 |
url |
jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDataTimeBehavior=convertToNull&useSSL=false |
数据库连接地址 |
username
|
root | 数据库连接用户名 |
password | 123456 | 数据库连接用户密码 |
initialSize | 10 | 连接池初始化连接数 |
minIdle | 1 | 连接池最小连接数 |
maxActive | 20 | 连接池最大活跃连接数 |
maxWait | 60000 | 客户端获取连接等待超时时间,单位为毫秒,此处的超时时间和创建连接超时时间是不一样的,客户端获取连接超时有可能是创建连接超时,也有可能是当前连接数达到最大值并且其他客户端正在使用,客户端一直排队等待可用连接超时了,所以尽量避免慢SQL,否则一旦可用连接被占用了且都在执行慢SQL,就会导致其他客户端长时间无法获取连接而超时 |
timeBetweenEvictionRunsMillis | 60000 | 连接空闲检测间隔时长,单位为毫秒,当连接长时间空闲时,有定时任务会间隔间隔一段时间检测一次,如果发现连接空闲时间足够长,则关闭连接 |
minEvictableIdleTimeMillis | 60000 | 连接最小生成时间,虽然空闲连接会被关闭,但是并非所有空闲的连接都会关闭,而是要看连接空闲了多长时间,比如配置了60000毫秒,那么当连接空闲超过1分钟时才会被关闭,否则可以继续空闲等待客户端 |
validationQuery | select 'X' | 检测SQL |
testwhileIdle | false | 空闲的时候检测执行validtionQuery严重连接是否有效,开启会消耗性能 |
testOnReturn | false | 归还连接时执行validationQuery验证连接是否有效,开启会消耗性能 |
poolPreparedStatements | true | 是否开启Prepared缓存,开启会提高重复查询的效率,但是会消耗一定的内存 |
maxOpenPreparedStatements | 20 | 每个Connection的prepared缓存语句数量 |
二、Druid源码解析
连接池的主要作用是提供连接给应用程序,所以需要实现数据源DataSource接口,Druid提供的数据源为DruidDataSource实现了DataSource接口,核心逻辑实际就是实现了DataSource接口的getConnection方法,在分析getConnecction方法实现逻辑之前,首先需要了解DruidDataSource的主要属性,分别如下:
1. /** 初始化连接数,默认为0 */ 2 protected volatile int initialSize = DEFAULT_INITIAL_SIZE; 3 /** 最大连接数,默认是8 */ 4 protected volatile int maxActive = DEFAULT_MAX_ACTIVE_SIZE; 5 /** 最小空闲连接数,默认是0 */ 6 protected volatile int minIdle = DEFAULT_MIN_IDLE; 7 /** 最大空闲连接数,默认数8 */ 8 protected volatile int maxIdle = DEFAULT_MAX_IDLE; 9 /** 最大等待超时时间, 默认为-1,表示不会超时 */ 10 protected volatile long maxWait = DEFAULT_MAX_WAIT;
DruidDataSource的getConnection方法逻辑如下:
1 /** 获取数据库连接*/ 2 public DruidPooledConnection getConnection() throws SQLException { 3 return getConnection(maxWait); 4 } 5 6 public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException { 7 /** 初始化*/ 8 init(); 9 /** 初始化过滤器*/ 10 if (filters.size() > 0) { 11 FilterChainImpl filterChain = new FilterChainImpl(this); 12 return filterChain.dataSource_connect(this, maxWaitMillis); 13 } else { 14 /** 直接获取连接*/ 15 return getConnectionDirect(maxWaitMillis); 16 } 17 }
获取数据库连接时首先需要对连接池进行初始化,然后才能从连接池中获取连接,分别对应了方法init方法和getConnectionDirect方法,两个方法逻辑分别如下
2.1、连接池的初始化
init方法逻辑如下:
1 /** 连接池初始化 */ 2 public void init() throws SQLException { 3 /** 如果已经初始化直接返回 */ 4 if (inited) { 5 return; 6 } 7 8 final ReentrantLock lock = this.lock; 9 try { 10 /*** 加锁处理 */ 11 lock.lockInterruptibly(); 12 } catch (InterruptedException e) { 13 throw new SQLException("interrupt", e); 14 } 15 try { 16 /** 1.创建数据源ID */ 17 this.id = DruidDriver.createDataSourceId(); 18 19 /** 2.初始化过滤器 */ 20 for (Filter filter : filters) { 21 filter.init(this); 22 } 23 24 /** 25 * 3.maxActive、maxActive、minIdle、initialSize等参数校验以及JDBC等对象初始化 26 * */ 27 28 /** 4.初始化连接数组,数组大小为最大连接数*/ 29 connections = new DruidConnectionHolder[maxActive]; 30 31 SQLException connectError = null; 32 33 /** 5.根据初始化大小,初始化数据库连接*/ 34 for (int i = 0, size = getInitialSize(); i < size; ++i) { 35 //1.创建连接 36 Connection conn = createPhysicalConnection(); 37 //2.将连接封装成DruidConnectionHolder对象 38 DruidConnectionHolder holder = new DruidConnectionHolder(this, conn); 39 //3.将连接添加到连接数组中 40 connections[poolingCount] = holder; 41 incrementPoolingCount();//连接池中连接数自增+1 42 } 43 44 /** 创建并开启日志线程 */ 45 createAndLogThread(); 46 /** 创建并开启创建连接线程*/ 47 createAndStartCreatorThread(); 48 /** 创建并开启销毁连接线程*/ 49 createAndStartDestroyThread(); 50 /** 等待 创建连接线程 和 销毁连接线程 全部开启才算初始化完成 */ 51 initedLatch.await(); 52 53 }finally { 54 /** 标记已经初始化*/ 55 inited = true; 56 /** 释放锁*/ 57 lock.unlock(); 58 } 59 }
连接池初始化的逻辑主要如下:
1、判断是否已经初始化,如果已经初始化直接跳出;如果没有初始化则继续初始化
2、防止并发初始化需要加锁处理
3、初始化过滤器并进行初始化参数校验
4、初始化连接数组,并根据配置的初始化大小创建指定数量的连接存入数组中,初始化的连接数就是传入的参数值initialSIze的值
5、创建并开启创建连接和销毁连接的线程
6、标记初始化完成并释放锁
连接池初始化时会创建执指定数量的连接,并存入数组中。但是通常情况下连接池中的连接数量不是固定不变的,通常需要随着并发量提高要增加,随着并发量小而减少。所以在初始化的时候分别创建了创建连接的线程和销毁连接的线程,用于动态的创建连接和销毁连接,从而达到连接池动态的增删连接的效果。
2.1.1、创建连接的线程createAndStartCreatorThread方法源码解析
1 protected void createAndStartCreatorThread() { 2 if (createScheduler == null) { 3 String threadName = "Druid-ConnectionPool-Create-" + System.identityHashCode(this); 4 createConnectionThread = new CreateConnectionThread(threadName); 5 createConnectionThread.start(); 6 return; 7 } 8 9 initedLatch.countDown(); 10 }
该方法的主要作用就是创建了一个创建连接的线程CreateConnectionThread对象,并且启动了线程,所以核心逻辑就是需要分析该线程主要的流程,逻辑如下:
1 /** 创建连接线程*/ 2 public class CreateConnectionThread extends Thread { 3 4 public CreateConnectionThread(String name){ 5 super(name); 6 this.setDaemon(true); 7 } 8 9 public void run() { 10 initedLatch.countDown(); 11 for (;;) { 12 try { 13 lock.lockInterruptibly(); 14 } catch (InterruptedException e2) { 15 break; 16 } 17 18 try { 19 /** 20 * poolingCount:连接池中的空闲连接数量 21 * notEmptyWaitThreadCount:等待连接的线程数量 22 * 当连接足够时,睡眠线程 23 * */ 24 // 必须存在线程等待,才创建连接 25 if (poolingCount >= notEmptyWaitThreadCount) { 26 empty.await(); 27 } 28 29 // 防止创建超过maxActive数量的连接 30 /** 31 * activeCount: 活跃的连接数 32 * maxActive: 最大线程数 33 * 当活跃连接数 + 空闲连接数 >= 最大连接数时 睡眠线程 34 * */ 35 if (activeCount + poolingCount >= maxActive) { 36 empty.await(); 37 continue; 38 } 39 40 } catch (InterruptedException e) { 41 break; 42 } finally { 43 lock.unlock(); 44 } 45 46 /** 47 * 当等待连接的线程超过空闲线程;并且总连接数没有超过最大连接数时,创建新连接 48 * */ 49 Connection connection = null; 50 51 try { 52 /** 1.创建新连接 */ 53 connection = createPhysicalConnection(); 54 } catch (SQLException e) { 55 LOG.error("create connection error", e); 56 break; 57 } 58 59 if (connection == null) { 60 continue; 61 } 62 /** 2.将连接放入连接池中 */ 63 put(connection); 64 } 65 } 66 }
逻辑并不复杂,就是在一个死循环中不断判断当前连接数是否够用,并且是否超过最大上限,如果满足条件就创建新的连接,并且将连接添加到连接池中。
在这里有一个Condition对象empty,该对象主要用于监视当前的连接池是否需要创建连接了,如果不需要创建连接则调用await进行等待,等待连接不足时进行唤醒。
当连接创建之后,会调用put方法将连接放到连接数组中,逻辑如下:
1 protected void put(Connection connection) { 2 DruidConnectionHolder holder = null; 3 try { 4 /** 1.将连接封装成功DruidConnectionHolder对象 */ 5 holder = new DruidConnectionHolder(DruidDataSource.this, connection); 6 } catch (SQLException ex) { 7 lock.lock(); 8 try { 9 /** 10 * createScheduler 是创建连接的线程池 11 * createTaskCount 是当前需要创建连接的任务个数 12 * 当线程池不为空时,任务个数减1 13 * */ 14 if (createScheduler != null) { 15 createTaskCount--; 16 } 17 } finally { 18 lock.unlock(); 19 } 20 LOG.error("create connection holder error", ex); 21 return; 22 } 23 24 lock.lock(); 25 try { 26 /** 2.存入连接池数组中 */ 27 connections[poolingCount] = holder; 28 /** 3.空闲连接数poolingCount自增*/ 29 incrementPoolingCount(); 30 if (poolingCount > poolingPeak) { 31 /** 4.超过峰值则记录连接数量的峰值 */ 32 poolingPeak = poolingCount; 33 poolingPeakTime = System.currentTimeMillis(); 34 } 35 /** 3.唤醒notEmpty,因为该连接是非初始化创建而是动态额外添加的,所以需要唤醒销毁线程准备销毁该连接 */ 36 notEmpty.signal(); 37 notEmptySignalCount++; 38 39 if (createScheduler != null) { 40 createTaskCount--; 41 42 if (poolingCount + createTaskCount < notEmptyWaitThreadCount // 43 && activeCount + poolingCount + createTaskCount < maxActive) { 44 /** 如果连接数还是不足,则继续唤醒empty */ 45 emptySignal(); 46 } 47 } 48 } finally { 49 lock.unlock(); 50 } 51 } 52 53 /** 唤醒empty */ 54 private void emptySignal() { 55 if (createScheduler == null) { 56 empty.signal(); 57 return; 58 } 59 60 if (createTaskCount >= maxCreateTaskCount) { 61 return; 62 } 63 64 if (activeCount + poolingCount + createTaskCount >= maxActive) { 65 return; 66 } 67 68 createTaskCount++; 69 CreateConnectionTask task = new CreateConnectionTask();//创建连接的Task逻辑和CreateConneactionThread线程的逻辑完全一致 70 createScheduler.submit(task); 71 }
2.2.1.2、销毁连接的线程createAndStartDestroyThread方法源码解析
销毁连接的线程方法逻辑基本和创建连接的逻辑相反,主要逻辑如下:
1 /** 销毁连接线程 */ 2 public class DestroyConnectionThread extends Thread { 3 4 public DestroyConnectionThread(String name){ 5 super(name); 6 this.setDaemon(true); 7 } 8 9 public void run() { 10 initedLatch.countDown(); 11 12 for (;;) { 13 // 从前面开始删除 14 try { 15 if (closed) { 16 break; 17 } 18 /** 如果设置了检查间隔,则睡眠线程指定时长,否则就默认睡眠1秒*/ 19 if (timeBetweenEvictionRunsMillis > 0) { 20 Thread.sleep(timeBetweenEvictionRunsMillis); 21 } else { 22 Thread.sleep(1000); // 23 } 24 25 if (Thread.interrupted()) { 26 break; 27 } 28 /** 执行销毁连接的任务 */ 29 destoryTask.run(); 30 } catch (InterruptedException e) { 31 break; 32 } 33 } 34 } 35 36 }
销毁连接的任务交给了DestroyTask来实现,逻辑如下:
1 /** 销毁连接任务*/ 2 public class DestroyTask implements Runnable { 3 4 @Override 5 public void run() { 6 /** 1.销毁超过最大空闲时间的连接 */ 7 shrink(true); 8 9 /** 2.强制回收超过超时时间的连接*/ 10 if (isRemoveAbandoned()) { 11 removeAbandoned(); 12 } 13 } 14 }
销毁连接的任务主要有两个核心逻辑:
1、销毁空闲连接
当一个连接长时间没有被使用,如果不及时清理就会造成资源浪费,所以需要定时检查空闲时间过长的连接进行断开连接销毁
2、回收超时连接
当一个连接被一个线程长时间占有没有被归还,有可能是程序出故障了或是有漏洞导致吃吃没有归还连接,这样就可能会导致连接池中的连接不够用,所以需要定时检查霸占连接时间过长的线程,如果超过规定时间没有归还连接,则强制回收该连接。
销毁空闲连接逻辑如下:
1 /** 连接空闲时间,默认为30分钟 */ 2 public static final long DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L; 3 4 /** 销毁空闲连接 */ 5 public void shrink(boolean checkTime) { 6 /** 1.需要从连接池中去除的连接列表 */ 7 final List<DruidConnectionHolder> evictList = new ArrayList<DruidConnectionHolder>(); 8 try { 9 lock.lockInterruptibly(); 10 } catch (InterruptedException e) { 11 return; 12 } 13 14 try { 15 /** 2.获取需要去除的个数 */ 16 final int checkCount = poolingCount - minIdle; 17 final long currentTimeMillis = System.currentTimeMillis(); 18 for (int i = 0; i < checkCount; ++i) { 19 DruidConnectionHolder connection = connections[i]; 20 /** 是否校验连接的空闲时间*/ 21 if (checkTime) { 22 long idleMillis = currentTimeMillis - connection.getLastActiveTimeMillis(); 23 /** 3.1.如果连接空闲时间超过设置的值,则去除*/ 24 if (idleMillis >= minEvictableIdleTimeMillis) { 25 evictList.add(connection); 26 } else { 27 break; 28 } 29 } else { 30 /** 3.2.如果不校验时间,则按顺序去除*/ 31 evictList.add(connection); 32 } 33 } 34 35 int removeCount = evictList.size(); 36 /** 4.从数组中将多余的连接移除*/ 37 if (removeCount > 0) { 38 System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount); 39 Arrays.fill(connections, poolingCount - removeCount, poolingCount, null); 40 poolingCount -= removeCount; 41 } 42 } finally { 43 lock.unlock(); 44 } 45 46 /** 5.依次断开被移除的连接 */ 47 for (DruidConnectionHolder item : evictList) { 48 Connection connection = item.getConnection(); 49 JdbcUtils.close(connection); 50 destroyCount.incrementAndGet(); 51 } 52 }
回收超时连接逻辑如下:
1 /** 强制回收连接 */ 2 public int removeAbandoned() { 3 int removeCount = 0; 4 5 long currrentNanos = System.nanoTime(); 6 7 /** 1.定义需要回收的连接列表*/ 8 List<DruidPooledConnection> abandonedList = new ArrayList<DruidPooledConnection>(); 9 10 synchronized (activeConnections) { 11 Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator(); 12 13 for (; iter.hasNext();) { 14 DruidPooledConnection pooledConnection = iter.next(); 15 if (pooledConnection.isRunning()) { 16 continue; 17 } 18 long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000); 19 /** 2.遍历判断超时未回收的连接,并加入列表中 */ 20 if (timeMillis >= removeAbandonedTimeoutMillis) { 21 iter.remove(); 22 pooledConnection.setTraceEnable(false); 23 abandonedList.add(pooledConnection); 24 } 25 } 26 } 27 28 if (abandonedList.size() > 0) { 29 /** 3.遍历回收连接列表,进行连接回收*/ 30 for (DruidPooledConnection pooledConnection : abandonedList) { 31 synchronized (pooledConnection) { 32 if (pooledConnection.isDisable()) { 33 continue; 34 } 35 } 36 /** 3.1.强制断开连接*/ 37 JdbcUtils.close(pooledConnection); 38 pooledConnection.abandond(); 39 removeAbandonedCount++; 40 removeCount++; 41 42 /** 3.2.日志打印*/ 43 if (isLogAbandoned()) { 44 StringBuilder buf = new StringBuilder(); 45 buf.append("abandon connection, owner thread: "); 46 buf.append(pooledConnection.getOwnerThread().getName()); 47 buf.append(", connected time nano: "); 48 buf.append(pooledConnection.getConnectedTimeNano()); 49 buf.append(", open stackTrace\n"); 50 51 StackTraceElement[] trace = pooledConnection.getConnectStackTrace(); 52 for (int i = 0; i < trace.length; i++) { 53 buf.append("\tat "); 54 buf.append(trace[i].toString()); 55 buf.append("\n"); 56 } 57 58 LOG.error(buf.toString()); 59 } 60 } 61 } 62 return removeCount; 63 }
2.2、连接池中获取连接
1 /** 从连接池中获取连接 */ 2 public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException { 3 int notFullTimeoutRetryCnt = 0; 4 for (;;) { 5 // handle notFullTimeoutRetry 6 DruidPooledConnection poolableConnection; 7 try { 8 /** 1.获取连接 */ 9 poolableConnection = getConnectionInternal(maxWaitMillis); 10 } catch (GetConnectionTimeoutException ex) { 11 if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) { 12 notFullTimeoutRetryCnt++; 13 if (LOG.isWarnEnabled()) { 14 LOG.warn("not full timeout retry : " + notFullTimeoutRetryCnt); 15 } 16 continue; 17 } 18 throw ex; 19 } 20 21 /** 2.判断获取的连接是否有效 */ 22 if (isTestOnBorrow()) { 23 boolean validate = testConnectionInternal(poolableConnection.getConnection()); 24 if (!validate) { 25 if (LOG.isDebugEnabled()) { 26 LOG.debug("skip not validate connection."); 27 } 28 /** 2.1 连接无效则抛弃连接 */ 29 Connection realConnection = poolableConnection.getConnection(); 30 discardConnection(realConnection); 31 continue; 32 } 33 } else { 34 Connection realConnection = poolableConnection.getConnection(); 35 if (realConnection.isClosed()) { 36 discardConnection(null); // 传入null,避免重复关闭 37 continue; 38 } 39 /** 3.如果没有判断连接有效性,则判断该连接是否空闲*/ 40 if (isTestWhileIdle()) { 41 final long currentTimeMillis = System.currentTimeMillis(); 42 final long lastActiveTimeMillis = poolableConnection.getConnectionHolder().getLastActiveTimeMillis(); 43 final long idleMillis = currentTimeMillis - lastActiveTimeMillis; 44 long timeBetweenEvictionRunsMillis = this.getTimeBetweenEvictionRunsMillis(); 45 if (timeBetweenEvictionRunsMillis <= 0) { 46 timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; 47 } 48 /** 4.如连接空闲时间过长,则强制校验连接的有效性 */ 49 if (idleMillis >= timeBetweenEvictionRunsMillis) { 50 boolean validate = testConnectionInternal(poolableConnection.getConnection()); 51 if (!validate) { 52 if (LOG.isDebugEnabled()) { 53 LOG.debug("skip not validate connection."); 54 } 55 discardConnection(realConnection); 56 continue; 57 } 58 } 59 } 60 } 61 /** 4.给连接添加监听,超过时间未归还,则强制回收该连接*/ 62 if (isRemoveAbandoned()) { 63 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); 64 poolableConnection.setConnectStackTrace(stackTrace); 65 poolableConnection.setConnectedTimeNano(); 66 poolableConnection.setTraceEnable(true); 67 68 synchronized (activeConnections) { 69 activeConnections.put(poolableConnection, PRESENT); 70 } 71 } 72 /** 5.设置是否自动提交 */ 73 if (!this.isDefaultAutoCommit()) { 74 poolableConnection.setAutoCommit(false); 75 } 76 return poolableConnection; 77 } 78 }
获取连接的逻辑步骤不多,首先是从连接池中获取连接,获取到连接之后根据配置项判断是否需要对连接进行有效性检测,防止获取到了一个无效的连接。
获取连接的方法getConnectionInternal方法逻辑如下:
1 /** 获取连接*/ 2 private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException { 3 /** 1.连接池状态判断*/ 4 if (closed) { 5 connectErrorCount.incrementAndGet(); 6 throw new DataSourceClosedException("dataSource already closed at " + new Date(closeTimeMillis)); 7 } 8 9 if (!enable) { 10 connectErrorCount.incrementAndGet(); 11 throw new DataSourceDisableException(); 12 } 13 14 final long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait); 15 final int maxWaitThreadCount = getMaxWaitThreadCount(); 16 17 DruidConnectionHolder holder; 18 try { 19 lock.lockInterruptibly(); 20 } catch (InterruptedException e) { 21 connectErrorCount.incrementAndGet(); 22 throw new SQLException("interrupt", e); 23 } 24 25 try { 26 /** 2.判断等待获取连接的线程是否超过最大值 */ 27 if (maxWaitThreadCount > 0) { 28 if (notEmptyWaitThreadCount >= maxWaitThreadCount) { 29 connectErrorCount.incrementAndGet(); 30 throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count " 31 + lock.getQueueLength()); 32 } 33 } 34 35 connectCount++; 36 if (maxWait > 0) { 37 /** 3.1 如果设置超时时间,则堵塞指定时长获取连接*/ 38 holder = pollLast(nanos); 39 } else { 40 /** 3.2 如果没有设置超时时间,则堵塞获取连接*/ 41 holder = takeLast(); 42 } 43 44 if (holder != null) { 45 activeCount++; 46 if (activeCount > activePeak) { 47 activePeak = activeCount; 48 activePeakTime = System.currentTimeMillis(); 49 } 50 } 51 } catch (InterruptedException e) { 52 connectErrorCount.incrementAndGet(); 53 throw new SQLException(e.getMessage(), e); 54 } catch (SQLException e) { 55 connectErrorCount.incrementAndGet(); 56 throw e; 57 } finally { 58 lock.unlock(); 59 } 60 61 /** 4.如果获取不到连接,则打印错误日志并抛异常 */ 62 if (holder == null) { 63 long waitNanos = waitNanosLocal.get(); 64 65 StringBuilder buf = new StringBuilder(); 66 buf.append("wait millis ")// 67 .append(waitNanos / (1000 * 1000))// 68 .append(", active " + activeCount)// 69 .append(", maxActive " + maxActive)// 70 ; 71 72 List<JdbcSqlStatValue> sqlList = this.getDataSourceStat().getRuningSqlList(); 73 for (int i = 0; i < sqlList.size(); ++i) { 74 if (i != 0) { 75 buf.append('\n'); 76 } else { 77 buf.append(", "); 78 } 79 JdbcSqlStatValue sql = sqlList.get(i); 80 buf.append("runningSqlCount "); 81 buf.append(sql.getRunningCount()); 82 buf.append(" : "); 83 buf.append(sql.getSql()); 84 } 85 86 String errorMessage = buf.toString(); 87 88 if (this.createError != null) { 89 throw new GetConnectionTimeoutException(errorMessage, createError); 90 } else { 91 throw new GetConnectionTimeoutException(errorMessage); 92 } 93 } 94 95 holder.incrementUseCount(); 96 /** 5.构造连接对象并返回 */ 97 DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder); 98 return poolalbeConnection; 99 }
获取连接主要看有没有设置超时时间,如果设置了超时时间则调用pollLast方法进行尝试获取连接,超时没有获取则返回空;takeLast方法是一直堵塞当前线程直到获取连接成功才会返回。
1 /** 一直堵塞获取连接*/ 2 DruidConnectionHolder takeLast() throws InterruptedException, SQLException { 3 try { 4 /** 1.如果当前空闲连接数为0 */ 5 while (poolingCount == 0) { 6 /** 2.发送信号等待创建连接*/ 7 emptySignal(); // send signal to CreateThread create connection 8 notEmptyWaitThreadCount++; 9 if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) { 10 notEmptyWaitThreadPeak = notEmptyWaitThreadCount; 11 } 12 try { 13 /** 等待信号*/ 14 notEmpty.await(); // signal by recycle or creator 15 } finally { 16 notEmptyWaitThreadCount--; 17 } 18 notEmptyWaitCount++; 19 20 if (!enable) { 21 connectErrorCount.incrementAndGet(); 22 throw new DataSourceDisableException(); 23 } 24 } 25 } catch (InterruptedException ie) { 26 notEmpty.signal(); // propagate to non-interrupted thread 27 notEmptySignalCount++; 28 throw ie; 29 } 30 31 decrementPoolingCount(); 32 /** 获取连接池最后一位的连接*/ 33 DruidConnectionHolder last = connections[poolingCount]; 34 /** 将数组对应位置置为空*/ 35 connections[poolingCount] = null; 36 return last; 37 }
1 private DruidConnectionHolder pollLast(long nanos) throws InterruptedException, SQLException { 2 long estimate = nanos; 3 4 for (;;) { 5 if (poolingCount == 0) { 6 emptySignal(); // send signal to CreateThread create connection 7 8 if (estimate <= 0) { 9 waitNanosLocal.set(nanos - estimate); 10 return null; 11 } 12 13 notEmptyWaitThreadCount++; 14 if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) { 15 notEmptyWaitThreadPeak = notEmptyWaitThreadCount; 16 } 17 18 try { 19 long startEstimate = estimate; 20 /** 等待信号指定时长*/ 21 estimate = notEmpty.awaitNanos(estimate); // signal by 22 // recycle or 23 // creator 24 notEmptyWaitCount++; 25 notEmptyWaitNanos += (startEstimate - estimate); 26 27 if (!enable) { 28 connectErrorCount.incrementAndGet(); 29 throw new DataSourceDisableException(); 30 } 31 } catch (InterruptedException ie) { 32 notEmpty.signal(); // propagate to non-interrupted thread 33 notEmptySignalCount++; 34 throw ie; 35 } finally { 36 notEmptyWaitThreadCount--; 37 } 38 39 if (poolingCount == 0) { 40 if (estimate > 0) { 41 continue; 42 } 43 44 waitNanosLocal.set(nanos - estimate); 45 return null; 46 } 47 } 48 49 decrementPoolingCount(); 50 DruidConnectionHolder last = connections[poolingCount]; 51 connections[poolingCount] = null; 52 53 return last; 54 } 55 }
takeLast和pollLast的逻辑基本上一直,主要是看等待连接时是一直等待还是超时等待,一般都会设置超时时间,防止程序一直堵塞着。这里又使用到了emptySignal和notEmptySignal, 后面仔细分析。
2.3、连接归还到连接池
当程序使用完连接需要将连接归还到线程池,通过会执行connection.close方法进行关闭,Druid中的连接对象为DruidPooledConnection,close方法中执行了回收的方法recycle(),该方法会将连接回收到连接池中,逻辑如下:
1 public void recycle() throws SQLException { 2 if (this.disable) { 3 return; 4 } 5 6 DruidConnectionHolder holder = this.holder; 7 if (holder == null) { 8 if (dupCloseLogEnable) { 9 LOG.error("dup close"); 10 } 11 return; 12 } 13 14 if (!this.abandoned) { 15 /** 获取数据源头像 */ 16 DruidAbstractDataSource dataSource = holder.getDataSource(); 17 /** 执行数据源的recycle方法进行连接回收*/ 18 dataSource.recycle(this); 19 } 20 21 this.holder = null; 22 conn = null; 23 transactionInfo = null; 24 closed = true; 25 }
主要步骤为先获取该连接所属的数据源对象,然后直接执行DataSource对象的recycle方法进行连接的回收,DruidDataSource中的recycle方法逻辑如下:
1 /** 2 * 回收连接 3 */ 4 protected void recycle(DruidPooledConnection pooledConnection) throws SQLException { 5 final DruidConnectionHolder holder = pooledConnection.getConnectionHolder(); 6 7 if (holder == null) { 8 LOG.warn("connectionHolder is null"); 9 return; 10 } 11 12 final Connection physicalConnection = holder.getConnection(); 13 14 if (pooledConnection.isTraceEnable()) { 15 synchronized (activeConnections) { 16 if (pooledConnection.isTraceEnable()) { 17 Object oldInfo = activeConnections.remove(pooledConnection); 18 if (oldInfo == null) { 19 if (LOG.isWarnEnabled()) { 20 LOG.warn("remove abandonded failed. activeConnections.size " + activeConnections.size()); 21 } 22 } 23 pooledConnection.setTraceEnable(false); 24 } 25 } 26 } 27 28 final boolean isAutoCommit = holder.isUnderlyingAutoCommit(); 29 final boolean isReadOnly = holder.isUnderlyingReadOnly(); 30 final boolean testOnReturn = this.isTestOnReturn(); 31 32 try { 33 // check need to rollback? 34 if ((!isAutoCommit) && (!isReadOnly)) { 35 pooledConnection.rollback(); 36 } 37 38 //校验回收线程和获取线程是否一致,并对连接持有对象进行重置 39 boolean isSameThread = pooledConnection.getOwnerThread() == Thread.currentThread(); 40 if (!isSameThread) { 41 synchronized (pooledConnection) { 42 holder.reset(); 43 } 44 } else { 45 holder.reset(); 46 } 47 48 if (holder.isDiscard()) { 49 return; 50 } 51 52 /** 校验连接*/ 53 if (testOnReturn) { 54 boolean validate = testConnectionInternal(physicalConnection); 55 if (!validate) { 56 JdbcUtils.close(physicalConnection); 57 58 destroyCount.incrementAndGet(); 59 60 lock.lock(); 61 try { 62 activeCount--; 63 closeCount++; 64 } finally { 65 lock.unlock(); 66 } 67 return; 68 } 69 } 70 71 if (!enable) { 72 /** 如果连接池不可用则丢弃连接*/ 73 discardConnection(holder.getConnection()); 74 return; 75 } 76 77 final long lastActiveTimeMillis = System.currentTimeMillis(); 78 lock.lockInterruptibly(); 79 try { 80 /** 统计数据修改*/ 81 activeCount--; 82 closeCount++; 83 /** 将连接添加到连接池数组的尾部 */ 84 putLast(holder, lastActiveTimeMillis); 85 recycleCount++; 86 } finally { 87 lock.unlock(); 88 } 89 } catch (Throwable e) { 90 holder.clearStatementCache(); 91 92 if (!holder.isDiscard()) { 93 this.discardConnection(physicalConnection); 94 holder.setDiscard(true); 95 } 96 97 LOG.error("recyle error", e); 98 recycleErrorCount.incrementAndGet(); 99 } 100 }
1 void putLast(DruidConnectionHolder e, long lastActiveTimeMillis) { 2 e.setLastActiveTimeMillis(lastActiveTimeMillis); 3 /** 将连接放到连接池数组的尾部 */ 4 connections[poolingCount] = e; 5 incrementPoolingCount(); 6 7 if (poolingCount > poolingPeak) { 8 poolingPeak = poolingCount; 9 poolingPeakTime = lastActiveTimeMillis; 10 } 11 /** 唤醒notEmpty */ 12 notEmpty.signal(); 13 notEmptySignalCount++; 14 }
三、Druid的实现细节
3.1、核心类
3.1.1、DruidDataSource类
DruidDataSource是Druid提供的数据类,实现了DataSource接口,实现了获取连接的getConnection方法,用于应用程序使用的数据对象,内部持有连接的数组DruidConnectionHolder[] connections表示连接池
3.1.2、DruidPooledConnection类
DruidPooledConnection表示数据库连接对象,应用程序获取DruidPooledConnection执行SQL,使用完毕调用close方法释放连接
3.1.3、DruidConnectionHolder类型
DruidConnectionHolder是DruidPooledConnection类的封装,表示连接池中持有的连接对象,连接池添加连接时实际是创建DruidConnectionHolder对象放入数组中,获取连接就是从数组尾部获取DruidConnectionHolder对象
3.2、ReentrantLock和Condition的使用
DruidDataSource内部有一个ReentrantLock lock对象和两个Condition对象,分别为empty和notEmpty,主要用于连接的创建和销毁的等待和通知。
数据库连接池初始化的时候会初始化固定数量的连接,但是随着应用程序的运行,数据库连接的需求往往是动态变化的,比如初始化时创建了10个连接,但是高峰期的时候需要15个连接才可以满足需求,此时连接池就需要动态的对连接池进行扩容,而等到高峰期过了之后,数据库连接池还需要将多余创建的5个连接进行释放,不然在空闲时间也会占据着连接造成资源的浪费。连接池中连接的动态增删就是依靠了empty和notEmpty这两个Condition对象。
empty用于通知创建连接,notEmpty用于通知应用获取应用
初始化时启动创建连接的线程,判断当前是否需要创建连接,如果不需要创建则调用empty.await()方法进行等待,等待empty被唤醒之后进行创建连接,一旦empty被唤醒就会创建连接,创建完成之后通知notEmpty,让用户不再阻塞,拿到连接对象。
客户端调用getConnection方法获取连接时,如果当前没有可用连接,则调用empty.signal()方法唤醒empty,并调用notEmpty.await()睡眠等待连接创建完成
3.3、Druid工作流程图
1、 getConnection方法流程如下:
2、创建连接线程和销毁连接线程流程如下: