Druid源码解析(四):获取连接(getPooledConnection)
DruidDataSource连接池实现了javaX.sql包中DataSource接口的全部方法。getConnection也来自于javaX.sql.DataSource接口。 而DruidPooledConnection也实现了接口java.sql.Connection。 这样就能在各种场景中通过这个接口来获取数据库连接。
一、filter处理-责任链模式
在执行getConnection方法的过程中,首先确认DataSource是否完成了初始化。由于 init方法采用了双重检测锁机制,如果初始化完成则不会再次执行,因此这里不会造成系统多次初始化。
然后判断是否存在filter,如果存在,则使用经典的责任链模式。 new了一个FilterChainImpl对象,而这个对象的构造函数 this 。 查看了一下,DruidDataSource的父类DruidAbstractDataSource正好实现了DataSourceProxy接口,也就是说,DruidDataSource本身就是一个DataSourceProxy。 这样做的好处是,FilterChainImpl本身不用维护任何存放filters的数组,这个数组可以直接复用DruidDataSource中的数据结构。 在FilterChainImpl中:
@Override public DruidPooledConnection getConnection() throws SQLException { return getConnection(maxWait); } public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException { init(); if (filters.size() > 0) { FilterChainImpl filterChain = new FilterChainImpl(this); return filterChain.dataSource_connect(this, maxWaitMillis); } else { return getConnectionDirect(maxWaitMillis); } }
此时调用dataSource_connect时,判断pos和filter的数量,如果 pos 小于 filter数量,则使用责任链模式继续调用下一个。
@Override public DruidPooledConnection dataSource_connect(DruidDataSource dataSource, long maxWaitMillis) throws SQLException { if (this.pos < filterSize) { DruidPooledConnection conn = nextFilter().dataSource_getConnection(this, dataSource, maxWaitMillis); return conn; } return dataSource.getConnectionDirect(maxWaitMillis); } private Filter nextFilter() { return getFilters() .get(pos++); }
在调用到不满足条件的时候,则调用 dataSource.getConnectionDirect(maxWaitMillis); 直接创建一个连接之后返回,而实际上如果我们没有 filter 时,调用的也是这个方法。
二、getConnectionDirect
getConnectionDirect方法也不是最终创建数据库连接的方法。 这个方法会通过一个for循环自旋,确保连接的创建。 在GetConnectionTimeoutException异常处理中,这个地方有一个重试次数notFullTimeoutRetryCount,每次重试的时间为maxWaitMillis。
for (;;) { // handle notFullTimeoutRetry DruidPooledConnection poolableConnection; try { //调用getConnectionInternal 获取连接 poolableConnection = getConnectionInternal(maxWaitMillis); } catch (GetConnectionTimeoutException ex) { //超时异常处理,判断是否达到最大重试次数 且连接池是否已满 if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) { notFullTimeoutRetryCnt++; if (LOG.isWarnEnabled()) { LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt); } continue; } throw ex; } ......
通过自旋的方式确保获取到连接。之后对获取到的连接进行检测,主要的检测参数有:
参数 |
说明 |
---|---|
testOnBorrow |
默认值通常为false,用在获取连接的时候执行validationQuery检测连接是否有效。这个配置会降低性能。 |
testOnReturn |
默认值通常为false,用在归还连接的时候执行validationQuery检测连接是否有效,这个配置会降低性能。 |
testWhileIdle |
这个值通常建议为true,连接空闲时间大于timeBetweenEvictionRunsMillis指定的毫秒,就会执行参数validationQuery指定的SQL来检测连接是否有效。这个参数会定期执行。 |
validationQuery |
用来检测连接是否有效的sql,如果validationQuery为空,那么testOnBorrow、testOnReturn、testWhileIdle这三个参数都不会起作用,配置参考:validationQuery=SELECT 1 |
//获取连接时检测 if (testOnBorrow) { boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn); if (!validate) { if (LOG.isDebugEnabled()) { LOG.debug("skip not validate connection."); } discardConnection(poolableConnection.holder); continue; } } else { //检测连接是否关闭 if (poolableConnection.conn.isClosed()) { discardConnection(poolableConnection.holder); // 传入null,避免重复关闭 continue; } //空闲检测 if (testWhileIdle) { final DruidConnectionHolder holder = poolableConnection.holder; long currentTimeMillis = System.currentTimeMillis(); long lastActiveTimeMillis = holder.lastActiveTimeMillis; long lastExecTimeMillis = holder.lastExecTimeMillis; long lastKeepTimeMillis = holder.lastKeepTimeMillis; if (checkExecuteTime && lastExecTimeMillis != lastActiveTimeMillis) { lastActiveTimeMillis = lastExecTimeMillis; } if (lastKeepTimeMillis > lastActiveTimeMillis) { lastActiveTimeMillis = lastKeepTimeMillis; } long idleMillis = currentTimeMillis - lastActiveTimeMillis; long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis; if (timeBetweenEvictionRunsMillis <= 0) { timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; } if (idleMillis >= timeBetweenEvictionRunsMillis || idleMillis < 0 // unexcepted branch ) { boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn); if (!validate) { if (LOG.isDebugEnabled()) { LOG.debug("skip not validate connection."); } discardConnection(poolableConnection.holder); continue; } } } }
上述检测过程都会调用testConnectionInternal(poolableConnection.holder, poolableConnection.conn);进行检测。
此外还有一个很重要的参数removeAbandoned。这个参数相关的配置参数有:
参数 |
说明 |
---|---|
removeAbandoned |
如果连接泄露,是否需要回收泄露的连接,默认false; |
logAbandoned |
如果回收了泄露的连接,是否要打印一条log,默认false; |
removeAbandonedTimeoutMillis |
连接回收的超时时间,默认5分钟; |
参数removeAbandoned的作用在于,如果有线程从Druid中获取到了连接并没有及时归还,那么Druid就会定期检测该连接是否会处于运行状态,如果不处于运行状态,则被获取时间超过removeAbandonedTimeoutMillis就会强制回收该连接。 这个检测的过程是在回收线程中完成的,在getConnection的过程中,只是判断该参数是否被设置,然后加上对应的标识。
if (removeAbandoned) { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); //设置 stackTrace poolableConnection.connectStackTrace = stackTrace; //设置setConnectedTimeNano poolableConnection.setConnectedTimeNano(); //打开traceEnable poolableConnection.traceEnable = true; activeConnectionLock.lock(); try { activeConnections.put(poolableConnection, PRESENT); } finally { activeConnectionLock.unlock(); } }
最后还需要对defaultAutoCommit参数进行处理:
if (!this.defaultAutoCommit) { poolableConnection.setAutoCommit(false); }
至此,一个getConnetion方法创建完毕。
三、getConnectionInternal
getConnectionInternal方法中创建连接: 首先判断连接池状态 closed 和enable状态是否正确,如果不正确则抛出异常退出。
if (closed) { connectErrorCountUpdater.incrementAndGet(this); throw new DataSourceClosedException("dataSource already closed at " + new Date(closeTimeMillis)); } if (!enable) { connectErrorCountUpdater.incrementAndGet(this); if (disableException != null) { throw disableException; } throw new DataSourceDisableException(); }
之后的逻辑:
for (boolean createDirect = false;;){ if(createDirect){ //直接创建连接的逻辑 } if (maxWaitThreadCount > 0 && notEmptyWaitThreadCount >= maxWaitThreadCount) { // 判断最大等待线程数如果大于0且notEmpty上的等待线程超过了这个值 那么抛出异常 } //其他相关参数检测 抛出异常 //判断如果createScheduler存在,且executor.getQueue().size()大于0 那么将启用createDirect逻辑,退出本持循环 if (createScheduler != null && poolingCount == 0 && activeCount < maxActive && creatingCountUpdater.get(this) == 0 && createScheduler instanceof ScheduledThreadPoolExecutor) { ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) createScheduler; if (executor.getQueue().size() > 0) { createDirect = true; continue; } } //如果maxWait大于0,调用 pollLast(nanos),反之则调用takeLast() //获取连接的核心逻辑 if (maxWait > 0) { holder = pollLast(nanos); } else { holder = takeLast(); } }
之后获得holder之后,通过holder产生连接。
holder.incrementUseCount(); DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder); return poolalbeConnection;
四、pollLast 与 takeLast
polllast的方法核心逻辑是自旋,判断是否有可用连接,然后发送empty消息,通知生产者线程可以创建连接。之后阻塞wait。只不过这个方法需要设置超时时间。
for (;;) { //如果没有可用的连接 if (poolingCount == 0) { emptySignal(); // send signal to CreateThread create connection estimate = notEmpty.awaitNanos(estimate); // signal by } //之后获取最后一个连接 DruidConnectionHolder last = connections[poolingCount]; }
while (poolingCount == 0) { emptySignal(); // send signal to CreateThread create connection try { notEmpty.await(); // signal by recycle or creator } finally { notEmptyWaitThreadCount--; } } decrementPoolingCount(); //最后获取数组中的最后一个连接。 DruidConnectionHolder last = connections[poolingCount]; connections[poolingCount] = null;
-----------------------------------------------------------
---------------------------------------------
朦胧的夜 留笔~~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律