稳,从数据库连接池 testOnBorrow 看架构设计 | 京东云技术团队

京东云开发者
• 阅读 428

本文从 Commons DBCP testOnBorrow 的作用机制着手,管中窥豹,从一点去分析数据库连接池获取的过程以及架构分层设计。

以下内容会按照每层的作用,贯穿分析整个调用流程。

1️⃣框架层 commons-pool

The indication of whether objects will be validated before being borrowed from the pool.

If the object fails to validate, it will be dropped from the pool, and we will attempt to borrow another.

testOnBorrow 不是 dbcp 定义的,是commons-pool 定义的。commons-pool 详细的定义了资源池使用的一套规范和运行流程。

/**
 * Borrow an object from the pool. get object from 资源池
 * @see org.apache.commons.pool2.impl.GenericObjectPool#borrowObject(long)
 */
public T borrowObject(final long borrowMaxWaitMillis) throws Exception {

    PooledObject<T> p = null;

    // if validation fails, the instance is destroyed and the next available instance is examined. 
    // This continues until either a valid instance is returned or there are no more idle instances available.
    while (p == null) {
        // If there is one or more idle instance available in the pool, 
        // then an idle instance will be selected based on the value of getLifo(), activated and returned.
        p = idleObjects.pollFirst();
        if (p != null) {
            // 设置 testOnBorrow 就会进行可用性校验
            if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) {
                boolean validate = false;
                Throwable validationThrowable = null;
                try {
                    // 具体的校验实现由实现类完成。
                    // see org.apache.commons.dbcp2.PoolableConnectionFactory
                    validate = factory.validateObject(p);
                } catch (final Throwable t) {
                    PoolUtils.checkRethrow(t);
                    validationThrowable = t;
                }
                if (!validate) {
                    try {
                        // 如果校验异常,会销毁该资源。
                        // obj is not valid and should be dropped from the pool
                        destroy(p);
                        destroyedByBorrowValidationCount.incrementAndGet();
                    } catch (final Exception e) {
                        // Ignore - validation failure is more important
                    }
                    p = null;
                }
            }
        }
    }

    return p.getObject();
}

2️⃣应用层 commons-dbcp

dbcp 是特定于管理数据库连接的资源池。

PoolableConnectionFactory is a PooledObjectFactory

PoolableConnection is a PooledObject

/**
 * @see PoolableConnectionFactory#validateObject(PooledObject)
 */
@Override
public boolean validateObject(final PooledObject<PoolableConnection> p) {
    try {
        /**
         * 检测资源池对象的创建时间,是否超过生存时间
         * 如果超过 maxConnLifetimeMillis, 不再委托数据库连接进行校验,直接废弃改资源
         * @see PoolableConnectionFactory#setMaxConnLifetimeMillis(long)
         */
        validateLifetime(p);
        // 委托数据库连接进行自我校验
        validateConnection(p.getObject());
        return true;
    } catch (final Exception e) {
        return false;
    }
}

/**
 * 数据库连接层的校验。具体到是否已关闭、是否与 server 连接可用
 * @see Connection#isValid(int)
 */
public void validateConnection(final PoolableConnection conn) throws SQLException {
    if(conn.isClosed()) {
        throw new SQLException("validateConnection: connection closed");
    }
    conn.validate(_validationQuery, _validationQueryTimeout);
}

3️⃣基础层 mysql-connector-java

Returns true if the connection has not been closed and is still valid.

这个是 java.sql.Connection 定义的规范。具体实现根据对应数据库的driver 来完成。使用某种机制用来探测连接是否可用。

/**
 * 调用 com.mysql.jdbc.MysqlIO, 发送ping 请求,检测是否可用
 * 对比 H2 数据库,是通过获取当前事务级别来检测连接是否可以。但是忽略了 timeout 配置,毕竟是 demo 数据库 😅
 */
public synchronized boolean isValid(int timeout) throws SQLException {
    if (this.isClosed()) {
        return false;
    } else {
        try {
            this.pingInternal(false, timeout * 1000);
            return true;
        } catch (Throwable var5) {
            return false;
        }
    }
}

参考:MySQL 的连接时长控制--interactive_timeout和wait_timeout_翔云123456的博客-CSDN博客

总结

  • commons-pool 定义资源的完整声明周期接口,包括:makeObject、activateObject、validateObject、passivateObject、destoryObject。资源池管理对象,通过实现这些接口即可实现资源控制。参考:org.apache.commons.pool2.PooledObjectFactory
  • 在校验过程中,牵涉到很多时间,包括资源池对象的创建时间、生存时间、数据库连接的超时时间、Mysql 连接空闲超时时间等。不同层为了服务可靠性,提供不同的时间配置。校验也是层层递进,最终委托到最底层来判断。
  • 校验过程中,对于连接也会由是否已关闭的校验(isClosed() )。包括PoolableConnection#isClosed, Connection#isClosed, Socket#isClosed。 同样也是层层保障,确保整个架构的可靠。💪
  • 定义一套完整严谨的规范和标准,比实现一个具体的功能或者特性要求更高 🎯。commons-pool 和 jdbc 定义了规范,commons-dbcp 和 mysql-connector-java 完成了具体的实现。有了规范和接口,组件和框架的对接和兼容才变为可能。

more 理解高可用

在阅读 MySQL Driver 源码过程中,有个点要特别记录下。以 MySQL Driver 创建连接为例,用重试连接实现可用性,这就是高可用。🎯

高可用不是一个口号,也不是复杂的概念和公式。能够实实在在体系化的解决一类问题就是架构的目的。结合上述的架构分层,如果解决问题的方案通用性好,并且实现很优雅,就是好的架构。

// autoReconnect 
public void createNewIO(boolean isForReconnect) throws SQLException {
    synchronized (getConnectionMutex()) {
        // jdbc.url autoReconnect 指定为 true,识别为 HighAvailability。emmm..... 🙉
        if (!getHighAvailability()) {
            connectOneTryOnly(isForReconnect, mergedProps);
            return;
        }
        // maxReconnects 默认为 3,重试失败的提示就是: Attempted reconnect 3 times. Giving up.
        connectWithRetries(isForReconnect, mergedProps);
    }
}

作者:京东物流 杨攀

来源:京东云开发者社区

点赞
收藏
评论区
推荐文章
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
sql注入
反引号是个比较特别的字符,下面记录下怎么利用0x00SQL注入反引号可利用在分隔符及注释作用,不过使用范围只于表名、数据库名、字段名、起别名这些场景,下面具体说下1)表名payload:select\from\users\whereuser\_id1limit0,1;!(https://o
Stella981 Stella981
2年前
Dubbo分层架构概述
本节我们从整体上来看看Dubbo的分层架构设计,架构分层是一个比较经典的模式,比如网络中的7层协议,每层执行固定的功能,上层依赖下层提供的功能,下层对上层提供功能,下层的改变对上层不可见,并且每层都是一个可被替换的组件。如下图是Dubbo官方提供的Dubbo的整体架构图:!(https://oscimg.oschina.net/osc
Wesley13 Wesley13
2年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
2年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
2年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
2年前
InfoQ 趋势报告:架构和设计领域技术演变详解
https://www.infoq.cn/article/R7lWXd0R4VFf3E0bB\38本文概述了我们对当前“架构和设计”领域的看法,这个领域侧重于基础设施模式、技术框架模式的实现,以及软件架构师必须掌握的设计流程和技能。关键要点:我们看到了“演化式架构”设计需求的增长,这种架构建立在可替换性设计和关注“胶水”
Stella981 Stella981
2年前
Jenkins 插件开发之旅:两天内从 idea 到发布(上篇)
本文首发于:Jenkins中文社区(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fjenkinszh.cn)!huashan(https://oscimg.oschina.net/oscnet/f499d5b4f76f20cf0bce2a00af236d10265.jpg)
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_