简介
在上篇文章中,了解了连接池的应用场景和本地运行了示例,本篇文章中,我们尝试来探索下 Alibaba Druid 数据库连接池的整体实现思路
连接池的大体实现思路
查询了阅读了一些资料:下面两篇还是写的比较好的
文中实现一个数据库连接池的核心如下:
连接池设计的基本原理是这样的:
(1)建立连接池对象(服务启动)。
(2)按照事先指定的参数创建初始数量的连接(即:空闲连接数)。
(3)对于一个访问请求,直接从连接池中得到一个连接。如果连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的连接;如果达到最大,则设定一定的超时时间,来获取连接。
(4)运用连接访问服务。
(5)访问服务完成,释放连接(此时的释放连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。
(6)释放连接池对象(服务停止、维护期间,释放连接池对象,并释放所有连接)。
摘抄自:一文读懂连接池技术原理、设计与实现(Python)
上面的配合上文中 Python 代码实现,感觉还是能体会到实现一个连接池的关键点的
源码探索
基于上面的思路,我们看出连接的获取和释放应该是实现的关键,下面我们开始尝试寻找下相关的代码
运行示例,debug 入口
首先得找突破点,翻一翻 test 文件,找到下面一个东西:src/test/java/com/alibaba/druid/pool/demo/Demo0.java
public class Demo0 extends TestCase {
private String jdbcUrl;
private String user;
private String password;
private String driverClass;
private int initialSize = 10;
private int minPoolSize = 1;
private int maxPoolSize = 2;
private int maxActive = 12;
protected void setUp() throws Exception {
jdbcUrl = "jdbc:h2:file:./demo-db";
user = "sa";
password = "sa";
driverClass = "org.h2.Driver";
}
public void test_0() throws Exception {
DruidDataSource dataSource = new DruidDataSource();
JMXUtils.register("com.alibaba:type=DruidDataSource", dataSource);
dataSource.setInitialSize(initialSize);
dataSource.setMaxActive(maxActive);
dataSource.setMinIdle(minPoolSize);
dataSource.setMaxIdle(maxPoolSize);
dataSource.setPoolPreparedStatements(true);
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(jdbcUrl);
dataSource.setPoolPreparedStatements(true);
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setValidationQuery("SELECT 1");
dataSource.setTestOnBorrow(true);
Connection conn = dataSource.getConnection();
conn.close();
System.out.println();
}
}
复制代码
源码中是不可以运行的,需要进行下面的修改才能成功运行起来:
我们在下面的两行代码处都打上断点:
Connection conn = dataSource.getConnection();
conn.close();
复制代码
创建和获取数据库连接相关代码
根据断点进入:Connection conn = dataSource.getConnection();
# DruidDataSource.java
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);
}
}
复制代码
在上面的代码中直接去获取了初始化和数据库连接,我们先去瞅一瞅初始化相关的东西
在初始化的 diam 中,我们看到一些符合我们猜想的观点的类型队列初始化的东西,如下代码:
# DruidDataSource.java
public void init() throws SQLException {
# 疑似初始化连接池
connections = new DruidConnectionHolder[maxActive];
evictConnections = new DruidConnectionHolder[maxActive];
keepAliveConnections = new DruidConnectionHolder[maxActive];
SQLException connectError = null;
if (createScheduler != null && asyncInit) {
for (int i = 0; i < initialSize; ++i) {
submitCreateTask(true);
}
} else if (!asyncInit) {
// init connections
// 这里就是根据配置初始化了初始数据量的连接
while (poolingCount < initialSize) {
try {
PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
connections[poolingCount++] = holder;
} catch (SQLException ex) {
LOG.error("init datasource error, url: " + this.getUrl(), ex);
if (initExceptionThrow) {
connectError = ex;
break;
} else {
Thread.sleep(3000);
}
}
}
}
} catch (SQLException e) {
} finally {
}
}
}
复制代码
继续跟到获取连接相关的代码:
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
int notFullTimeoutRetryCnt = 0;
for (;;) {
// handle notFullTimeoutRetry
DruidPooledConnection poolableConnection;
try {
// 获取连接
poolableConnection = getConnectionInternal(maxWaitMillis);
} catch (GetConnectionTimeoutException ex) {
}
return poolableConnection;
}
}
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
DruidConnectionHolder holder;
for (;;) {
if (maxWait > 0) {
holder = pollLast(nanos);
} else {
// 取最后一个
holder = takeLast();
}
// 相关统计的计算
if (holder != null) {
if (holder.discard) {
continue;
}
activeCount++;
holder.active = true;
if (activeCount > activePeak) {
activePeak = activeCount;
activePeakTime = System.currentTimeMillis();
}
}
} catch (InterruptedException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException(e.getMessage(), e);
} catch (SQLException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw e;
} finally {
lock.unlock();
}
break;
}
DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);
return poolalbeConnection;
}
DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
decrementPoolingCount();
// 从init初始化中变量中获取最后一个连接
DruidConnectionHolder last = connections[poolingCount];
connections[poolingCount] = null;
return last;
}
复制代码
通过上面的代码,我们大致知道了获取链接的关键代码:
关闭连接
下面我们跟踪关闭连接的入口看下:conn.close();
# DruidPooledConnection.java
@Override
public void close() throws SQLException {
try {
List<Filter> filters = dataSource.getProxyFilters();
if (filters.size() > 0) {
} else {
// 回收的入口
recycle();
}
} finally {
}
}
public void recycle() throws SQLException {
if (!this.abandoned) {
DruidAbstractDataSource dataSource = holder.getDataSource();
// 进入回收逻辑
dataSource.recycle(this);
}
}
复制代码
上面是回收的入口逻辑,下面是具体的回收核心处理逻辑,可以看到设置了一些标识位,和把连接放回等操作,没有具体的关闭物理连接的逻辑
# DruidDataSource.java
/**
* 回收连接
*/
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
final DruidConnectionHolder holder = pooledConnection.holder;
lock.lock();
try {
if (holder.active) {
activeCount--;
holder.active = false;
}
closeCount++;
result = putLast(holder, currentTimeMillis);
recycleCount++;
} finally {
lock.unlock();
}
}
复制代码
总结
本篇文中,查阅了连接池实现的一些文章,然后对应在 Alibaba Druid 中定位到了符合我们猜想的核心代码:
当然还有其他的细节,留待后面再探索
参考链接
评论