写点什么

Alibaba Druid 源码阅读(二) 数据库连接池实现初步探索

作者:
  • 2021 年 11 月 12 日
  • 本文字数:4533 字

    阅读完需:约 15 分钟

简介

在上篇文章中,了解了连接池的应用场景和本地运行了示例,本篇文章中,我们尝试来探索下 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(); }}
复制代码


源码中是不可以运行的,需要进行下面的修改才能成功运行起来:


  • 1.将 maxActive 设置比 initSize 大:在运行中总是提示初始连接数最大活跃数,所以我们需要改下让初始连接数不小于最大活跃连接数

  • 2.替换成内存数据(不替换也可以运行,这个只是单纯修改玩玩)


我们在下面的两行代码处都打上断点:


        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.javapublic 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 中定位到了符合我们猜想的核心代码:


  • 初始化连接:根据设置的初始连接数,初始化相应数量的链接,看到目前使用的是一个简单的数组结构来保存的

  • 获取链接:目前看到的是取最后一个

  • 回收连接:不关闭物理连接,重置一些标识位


当然还有其他的细节,留待后面再探索

参考链接

发布于: 2 小时前阅读数: 4
用户头像

关注

还未添加个人签名 2018.09.09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
Alibaba Druid 源码阅读(二) 数据库连接池实现初步探索