美文网首页MySQLDruidDruid
Druid配置参数详解-testOnBorrow

Druid配置参数详解-testOnBorrow

作者: codeimport | 来源:发表于2020-01-03 10:58 被阅读0次

    Druid配置参数详解-testOnBorrow

    Druid是一个由阿里开源的数据库连接池,Druid的配置非常丰富,但是设置不当会对生产环境造成严重影响,网上Druid的资料虽多,但大部分都是互相复制粘贴,有很多不准确甚至完全错误的描述,Druid已经开源很久,而且作者WenShao的工作重心也已经不在Druid上,有些功能估计他自己都不太了解了。本系列将从源代码的角度分析Druid目前的最新版本(1.1.21)各个常用的配置项的具体含义以及是怎么起作用的。

    画外音:目前Druid在开源中国举办的2019年度最受欢迎中国开源软件中排名第7名,支持Druid的朋友可以去投票哇。2019年度最受欢迎中国开源软件

    testOnBorrow是什么意思?

    testOnBorrow:如果为true(默认false),当应用向连接池申请连接时,连接池会判断这条连接是否是可用的。

    testOnBorrow什么时候会用到?

    这个参数主要在DruidDataSource的getConnectionDirect方法中用到

        public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
            int notFullTimeoutRetryCnt = 0;
            for (;;) {
                // handle notFullTimeoutRetry
                DruidPooledConnection poolableConnection;
                try {
                    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;
                }
                //测试即将返回的连接是否可用
                if (testOnBorrow) {
                    boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                    if (!validate) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("skip not validate connection.");
                        }
    
                        discardConnection(poolableConnection.holder);
                        continue;
                    }
                }
            //。。。
            }
    

    连接池是如何判断连接是否有效的?

    • 如果是常用的数据库,则使用${DBNAME}ValidConnectionChecker进行判断,比如Mysql数据库,使用MySqlValidConnectionChecker的isValidConnection进行判断;
    • 如果是其他数据库,则使用validationQuery判断;
      具体验证规则可查看:Druid配置参数详解-validationQuery
        protected boolean testConnectionInternal(DruidConnectionHolder holder, Connection conn) {
            String sqlFile = JdbcSqlStat.getContextSqlFile();
            String sqlName = JdbcSqlStat.getContextSqlName();
    
            if (sqlFile != null) {
                JdbcSqlStat.setContextSqlFile(null);
            }
            if (sqlName != null) {
                JdbcSqlStat.setContextSqlName(null);
            }
            try {//如果是常用的数据库,则使用相关数据库的validConnectionChecker进行验证,比如MysqlValidConnectionChecker,否则使用validationQuery验证
                if (validConnectionChecker != null) {
                    boolean valid = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);
                    long currentTimeMillis = System.currentTimeMillis();
                    if (holder != null) {
                        holder.lastValidTimeMillis = currentTimeMillis;
                        holder.lastExecTimeMillis = currentTimeMillis;
                    }
    
                    if (valid && isMySql) { // unexcepted branch
                        long lastPacketReceivedTimeMs = MySqlUtils.getLastPacketReceivedTimeMs(conn);
                        if (lastPacketReceivedTimeMs > 0) {
                            long mysqlIdleMillis = currentTimeMillis - lastPacketReceivedTimeMs;
                            if (lastPacketReceivedTimeMs > 0 //
                                    && mysqlIdleMillis >= timeBetweenEvictionRunsMillis) {
                                discardConnection(holder);
                                String errorMsg = "discard long time none received connection. "
                                        + ", jdbcUrl : " + jdbcUrl
                                        + ", jdbcUrl : " + jdbcUrl
                                        + ", lastPacketReceivedIdleMillis : " + mysqlIdleMillis;
                                LOG.error(errorMsg);
                                return false;
                            }
                        }
                    }
    
                    if (valid && onFatalError) {
                        lock.lock();
                        try {
                            if (onFatalError) {
                                onFatalError = false;
                            }
                        } finally {
                            lock.unlock();
                        }
                    }
    
                    return valid;
                }
    
                if (conn.isClosed()) {
                    return false;
                }
    
                if (null == validationQuery) {
                    return true;
                }
    
                Statement stmt = null;
                ResultSet rset = null;
                try {
                    stmt = conn.createStatement();
                    if (getValidationQueryTimeout() > 0) {
                        stmt.setQueryTimeout(validationQueryTimeout);
                    }
                    rset = stmt.executeQuery(validationQuery);
                    if (!rset.next()) {
                        return false;
                    }
                } finally {
                    JdbcUtils.close(rset);
                    JdbcUtils.close(stmt);
                }
    
                if (onFatalError) {
                    lock.lock();
                    try {
                        if (onFatalError) {
                            onFatalError = false;
                        }
                    } finally {
                        lock.unlock();
                    }
                }
    
                return true;
            } catch (Throwable ex) {
                // skip
                return false;
            } finally {
                if (sqlFile != null) {
                    JdbcSqlStat.setContextSqlFile(sqlFile);
                }
                if (sqlName != null) {
                    JdbcSqlStat.setContextSqlName(sqlName);
                }
            }
        }
    
        public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
            if (conn.isClosed()) {
                return false;
            }
            //如果数据库驱动有pingInternal方法,则使用pingInternal方法判断,否则使用validateQuery进行判断
            if (usePingMethod) {
                if (conn instanceof DruidPooledConnection) {
                    conn = ((DruidPooledConnection) conn).getConnection();
                }
    
                if (conn instanceof ConnectionProxy) {
                    conn = ((ConnectionProxy) conn).getRawObject();
                }
    
                if (clazz.isAssignableFrom(conn.getClass())) {
                    if (validationQueryTimeout <= 0) {
                        validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;
                    }
    
                    try {
                        ping.invoke(conn, true, validationQueryTimeout * 1000);
                    } catch (InvocationTargetException e) {
                        Throwable cause = e.getCause();
                        if (cause instanceof SQLException) {
                            throw (SQLException) cause;
                        }
                        throw e;
                    }
                    return true;
                }
            }
    
            String query = validateQuery;
            if (validateQuery == null || validateQuery.isEmpty()) {
                query = DEFAULT_VALIDATION_QUERY;
            }
    
            Statement stmt = null;
            ResultSet rs = null;
            try {
                stmt = conn.createStatement();
                if (validationQueryTimeout > 0) {
                    stmt.setQueryTimeout(validationQueryTimeout);
                }
                rs = stmt.executeQuery(query);
                return true;
            } finally {
                JdbcUtils.close(rs);
                JdbcUtils.close(stmt);
            }
    
        }
    

    如果验证不通过怎么办?

    验证不通过则会直接关闭该连接,并重新从连接池获取下一条连接;

                //测试即将返回的连接是否可用
                if (testOnBorrow) {
                    boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                    if (!validate) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("skip not validate connection.");
                        }
                        //验证不通过则直接剔除该连接,并重新获取下一条连接
                        discardConnection(poolableConnection.holder);
                        continue;
                    }
                }
    

    总结

    testOnBorrow能够确保我们每次都能获取到可用的连接,但如果设置成true,则每次获取连接的时候都要到数据库验证连接有效性,这在高并发的时候会造成性能下降,可以将testOnBorrow设成false,testWhileIdle设置成true这样能获得比较好的性能。

    相关文章

      网友评论

        本文标题:Druid配置参数详解-testOnBorrow

        本文链接:https://www.haomeiwen.com/subject/nhkzoctx.html