Druid源码分析(七) recycle 主动回收连接


title: 归还连接池
date: 2022-05-17 09:17


连接什么时候归还

当逻辑走到 connection 的 close 方法时,归还连接.Druid 实现了 Connection 方法,并且在此之前封装了自己的 recycle 方法

void close() throws SQLException

DruidPooledConnection 的 recycle 方法

DruidPooledConnection 类中,封装了 recycle 方法,实际最终调用的是DruidDataSourcerecycle 方法.再通过DruidDataSourcerecycle 方法调用Conectionclose 方法

DruidPooledConnection,DruidDataSource,Conection的关系

Druid回收连接到连接池简要类图.jpg

作用

  • 职责剥离,最底层的Conection 方法负责与数据库物理连接的关闭工作.
  • DruidDataSourcerecycle 方法作为 Conection 的上层.对自己封装的 DruidConnectionHolder 进行清理,清理后统计数值的变化,以及归还(testOnReturn)时的校验,DruidDataSourcerecycle 方法只暴露给 DruidPooledConnection 调用.
  • DruidPooledConnection 又在 DruidDataSource 只上封装了一层,对外提供方法供应用层显示调用,做一些前置校验判断,最终调用 DruidDataSourcerecycle 方法

DruidPooledConnection 的 recycle 源码分析

/**
     * 可以被显示调用 conn.recycle();
     * @throws SQLException
*/
public void recycle() throws SQLException {
    // default disable = false
    if (this.disable) {
        return;
    }

    DruidConnectionHolder holder = this.holder;
    if (holder == null) {
        // 是否重复关闭日志开启状态 走到这里说明holder已经没了,不需要回收了.都没了~
        if (dupCloseLogEnable) {
            LOG.error("dup close");
        }
        return;
    }

    // 判断这个连接是不是被遗弃的,如果被遗弃了也不用回收
    if (!this.abandoned) {
        // 获取这个连接对应的数据源.因为多数据源有可能共享一个连接池
        DruidAbstractDataSource dataSource = holder.getDataSource();
        // 最终调用的是DruidDataSource的recycle方法
        dataSource.recycle(this);
    }

    // 如果 !this.abandoned =false 那么就不会回收
    this.holder = null;
    conn = null;
    // 这个链接当前的事务信息
    transactionInfo = null;
    closed = true;
}

DruidAbstractDataSource的recycle抽象方法

该父类抽象了回收方法的定义,可扩展让不同具现化的数据源实现

// DriudDataSource继承了父类DruidAbstractDataSource的抽象方法,子类具体实现了该方法
protected abstract void recycle(DruidPooledConnection pooledConnection)

DruidDataSource 的 recycle 具体实现

/**
     * 回收连接
     */
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
    // 获取连接池中的holder
    final DruidConnectionHolder holder = pooledConnection.holder;

    if (holder == null) {
        LOG.warn("connectionHolder is null");
        return;
    }

    if (logDifferentThread //
            // 是否开启了异步回收 默认false
            && (!isAsyncCloseConnectionEnable()) //
            // 如果当前线程和创建连接的线程不是一个
            && pooledConnection.ownerThread != Thread.currentThread()//
    ) {
        LOG.warn("get/close not same thread");
    }

    // 获取物理连接
    final Connection physicalConnection = holder.conn;

    // pooledConnection.traceEnable与activeConnections都是与removeAbandoned机制相关的参数
    if (pooledConnection.traceEnable) {
        Object oldInfo = null;
        activeConnectionLock.lock();
        try {
            if (pooledConnection.traceEnable) {
                oldInfo = activeConnections.remove(pooledConnection);
                pooledConnection.traceEnable = false;
            }
        } finally {
            activeConnectionLock.unlock();
        }
        if (oldInfo == null) {
            if (LOG.isWarnEnabled()) {
                LOG.warn("remove abandonded failed. activeConnections.size " + activeConnections.size());
            }
        }
    }

    // 是否自动提交
    final boolean isAutoCommit = holder.underlyingAutoCommit;
    // 是不是只读,影响事务处理
    final boolean isReadOnly = holder.underlyingReadOnly;
    // 是否归还时验证
    final boolean testOnReturn = this.testOnReturn;

    try {
        // check need to rollback?
        // 如果不是自动提交 并且 是写操作
        if ((!isAutoCommit) && (!isReadOnly)) {
            // 那么就回滚..这说明回滚操作也会走到这
            pooledConnection.rollback();
        }

        // reset holder, restore default settings, clear warnings
        boolean isSameThread = pooledConnection.ownerThread == Thread.currentThread();
        // todo 如果不是同一个线程,reset,为啥会是不通线程?不通线程要上锁?
        if (!isSameThread) {
            final ReentrantLock lock = pooledConnection.lock;
            lock.lock();
            try {
                holder.reset();
            } finally {
                lock.unlock();
            }
        } else {
            // 如果是同一个线程 也要充值
            holder.reset();
        }

        // 如果是丢弃 那么不做回收
        if (holder.discard) {
            return;
        }

        //
        if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) {
            discardConnection(holder);
            return;
        }

        // 如果已经关闭了物理连接 相关的统计数扣减
        if (physicalConnection.isClosed()) {
            lock.lock();
            try {
                if (holder.active) {
                    // 活动线程--
                    activeCount--;
                    // 活动状态
                    holder.active = false;
                }
                // 关闭数量++
                closeCount++;
            } finally {
                lock.unlock();
            }
            return;
        }

        // 如果开启了归还验证
        if (testOnReturn) {
            boolean validate = testConnectionInternal(holder, physicalConnection);
            // 验证不通过销毁这个连接
            if (!validate) {
                // 直接关闭
                JdbcUtils.close(physicalConnection);
                // 销毁数++
                destroyCountUpdater.incrementAndGet(this);

                // 锁统计线程 扣减
                lock.lock();
                try {
                    if (holder.active) {
                        activeCount--;
                        holder.active = false;
                    }
                    closeCount++;
                } finally {
                    lock.unlock();
                }
                return;
            }
        }

        // String类型initSchema 判断不严谨
        if (holder.initSchema != null) {
            holder.conn.setSchema(holder.initSchema);
            holder.initSchema = null;
        }

        if (!enable) {
            // 丢弃这个连接 里面jdbc close,close成功把这个holder的discard=true,避免对一个连接的重复关闭
            discardConnection(holder);
            return;
        }

        boolean result;
        final long currentTimeMillis = System.currentTimeMillis();

        // phyTimeoutMillis 物理连接超时配置,默认-1,是毫秒数
        if (phyTimeoutMillis > 0) {
            // 当前时间 - 连接时间 大于了 配置的物理连接超时时间,回收
            long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;
            if (phyConnectTimeMillis > phyTimeoutMillis) {
                discardConnection(holder);
                return;
            }
        }

        // 统计操作都要锁,为了准确性
        lock.lock();
        try {
            if (holder.active) {
                activeCount--;
                holder.active = false;
            }
            closeCount++;

            result = putLast(holder, currentTimeMillis);
            recycleCount++;
        } finally {
            lock.unlock();
        }

        if (!result) {
            JdbcUtils.close(holder.conn);
            LOG.info("connection recyle failed.");
        }
    } catch (Throwable e) {
        holder.clearStatementCache();

        if (!holder.discard) {
            discardConnection(holder);
            holder.discard = true;
        }

        LOG.error("recyle error", e);
        recycleErrorCountUpdater.incrementAndGet(this);
    }
}
    
/**
 * 关闭链接
 * @param holder
 */
public void discardConnection(DruidConnectionHolder holder) {
    if (holder == null) {
        return;
    }

    // 获取 connection对象
    Connection conn = holder.getConnection();
    if (conn != null) {
        JdbcUtils.close(conn);
    }

    // 上锁开始修改统计值
    lock.lock();
    try {
        // 避免重复关闭报错
        if (holder.discard) {
            return;
        }

        if (holder.active) {
            // 活跃数--
            activeCount--;
            // 该holder的活跃状态改成false
            holder.active = false;
        }
        // 回收数++
        discardCount++;

        holder.discard = true;

        // 如果活跃数小于了最小活跃数,就唤醒生产者允许继续创建线程直到minIdle
        if (activeCount <= minIdle) {
            emptySignal();
        }
    } finally {
        lock.unlock();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355

推荐阅读更多精彩内容