testWhileIdle和testOnBorrow

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

  • testWhileIdle是什么意思?

testWhileIdle:如果为true(默认true),当应用向连接池申请连接,并且testOnBorrow为false时,连接池将会判断连接是否处于空闲状态,如果是,则验证这条连接是否可用。

  • testWhileIdle什么时候会起作用?

  1. 获取连接时;
  2. testOnBorrow==false;
  3. testWhileIdle==true;

使用代码在DruidDataSource的getConnectionDirect方法
注意:此时判断连接空闲的依据是空闲时间大于timeBetweenEvictionRunsMillis(默认1分钟),并不是使用minEvictableIdleTimeMillis跟maxEvictableIdleTimeMillis也就是说如果连接空闲时间超过一分钟就测试一下连接的有效性,但并不是直接剔除;而如果空闲时间超过了minEvictableIdleTimeMillis则会直接剔除。

if (testOnBorrow) {
                boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                if (!validate) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("skip not validate connection.");
                    }

                    discardConnection(poolableConnection.holder);
                    continue;
                }
            } else {
                if (poolableConnection.conn.isClosed()) {
                    discardConnection(poolableConnection.holder); // 传入null,避免重复关闭
                    continue;
                }

                if (testWhileIdle) {
                    final DruidConnectionHolder holder = poolableConnection.holder;
                    long currentTimeMillis             = System.currentTimeMillis();
                    long lastActiveTimeMillis          = holder.lastActiveTimeMillis;
                    long lastExecTimeMillis            = holder.lastExecTimeMillis;
                    long lastKeepTimeMillis            = holder.lastKeepTimeMillis;

                    if (checkExecuteTime
                            && lastExecTimeMillis != lastActiveTimeMillis) {
                        lastActiveTimeMillis = lastExecTimeMillis;
                    }

                    if (lastKeepTimeMillis > lastActiveTimeMillis) {
                        lastActiveTimeMillis = lastKeepTimeMillis;
                    }

                    long idleMillis                    = currentTimeMillis - lastActiveTimeMillis;

                    long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;

                    if (timeBetweenEvictionRunsMillis <= 0) {
                        timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
                    }

                    if (idleMillis >= timeBetweenEvictionRunsMillis
                            || idleMillis < 0 // unexcepted branch
                            ) {
                        boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                        if (!validate) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("skip not validate connection.");
                            }

                            discardConnection(poolableConnection.holder);
                             continue;
                        }
                    }
                }
            }
  • 总结

testWhileIdle的作用跟testOnBorrow是差不多的,都是在获取连接的时候测试连接的有效性,如果两者都为true,则testOnBorrow优先级高,则不会使用到testWhileIdle。

  • 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判断;
    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这样能获得比较好的性能。


作者:codeimport
链接:https://www.jianshu.com/p/edb6a91285be
来源:简书

从整体上看,您的配置文件看起来合理,并包含了多个关键设置项。以下是针对每个部分的一些分析建议: ### 1. **InfluxDB 配置** ```yaml influx: url: http://192.168.3.60:8083 database: mydb retentionPolicy: autogen username: nic password: 123456 ``` - `url` 设置为 `http://192.168.3.60:8083`,这表明 InfluxDB 正在 IP 地址 `192.168.3.60` 上监听端口 `8083`。 - `username` `password` 分别指定用户凭据为 `nic` 密码 `123456`。 - 您选择了默认的保留策略 `autogen`,这是合理的初始选择。 **注意点:** - 确保目标机器上的防火墙规则允许访问该 URL 端口。 - 如果未来需要更高的安全性,则应考虑启用 HTTPS 支持并对认证机制加强保护措施(如使用 OAuth 或 Token 认证)。 --- ### 2. **Spring Boot Server Port** ```yaml server: port: 8083 ``` - 您将 Spring Boot 应用程序绑定到了端口 `8083`。 - 这是一个有效的做法,但在生产环境中,请避免与其他服务冲突,并确认所选端口未被占用。 --- ### 3. **MySQL 数据源配置** ```yaml spring: datasource: username: root password: q12w3e4r url: jdbc:mysql://127.0.0.1:9991/wm_admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&rewriteBatchedStatements=true driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource initialSize: 1 minIdle: 1 maxActive: 1 maxWait: 50000 timeBetweenEvictionRunsMillis: 30000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true filters: stat,wall maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 ``` 这部分定义了一个基于 Alibaba Druid 的 MySQL 数据池连接。以下是一些需要注意的地方: - 使用了 JDBC 协议并通过 `jdbc:mysql:` 方式指定了数据库位置及附加参数。 - 包含丰富的监控选项,例如慢查询日志、合并 SQL 统计等。 - 参数限制较严格,尤其是最大活动连接数只设成了 `maxActive: 1` ,可能会成为瓶颈问题所在。对于高并发场景来说,通常会将其值提升至更高水平(例如 20 到 100),具体取决于硬件资源情况以及业务需求量级大小差异程度的不同而有所区别对待而已啦! --- ### 4. **MyBatis Mapper Locations** ```yaml mybatis: mapper-locations: classpath:mapperxml/*.xml ``` - 映射器 XML 文件位于 `classpath:mapperxml/` 目录下,所有以 `.xml` 结尾的文件都会自动加载进来用于解析持久化层逻辑描述内容信息数据结构特征等方面的需求满足与否取决于实际情况怎样才行啊亲~ --- ### 5. **Socket Server Ports** ```yaml socketServerPort: port001: 54321 port002: 33333 ``` 这两行代码似乎是在声明两个自定义套接字服务器侦听的服务端口号分别是 `54321` `33333` 。不过如果想让这些数字真正生效的话,则还必须额外编写相应的 Java Socket 编程片段去实际开启对应进程实例才可以哟~ --- ### 总结评价 总体而言,这份 YAML 配置非常全面且详细地涵盖了多种不同类型的数据存储媒介之间的协同工作关系构建思路框架体系等等一系列复杂难懂的专业术语表达出来的意思其实就是说它很不错咯哈哈哈~但是也有一些小地方值得进一步探讨改进之处哦比如说那个 MySQL 最大活跃线程数量是不是有点太少了呀?还有关于那些所谓的“安全加密传输协议”之类的玩意儿貌似也没有涉及到耶!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值