org.apache.tomcat.jdbc.pool.ConnectionPool

查看源码

private PooledConnection borrowConnection(int wait, String username, String password) throws SQLException {
 
        if (isClosed()) {
            throw new SQLException("Connection pool closed.");
        } //end if
 
        //get the current time stamp
        long now = System.currentTimeMillis();
        //see if there is one available immediately
        PooledConnection con = idle.poll();
 
        while (true) {
            if (con!=null) {
                //configure the connection and return it
                PooledConnection result = borrowConnection(now, con, username, password);
                borrowedCount.incrementAndGet();
                if (result!=null) return result;
            }
 
            //if we get here, see if we need to create one
            //this is not 100% accurate since it doesn't use a shared
            //atomic variable - a connection can become idle while we are creating
            //a new connection
            if (size.get() < getPoolProperties().getMaxActive()) {
                //atomic duplicate check
                if (size.addAndGet(1) > getPoolProperties().getMaxActive()) {
                    //if we got here, two threads passed through the first if
                    size.decrementAndGet();
                } else {
                    //create a connection, we're below the limit
                    return createConnection(now, con, username, password);
                }
            } //end if
 
            //calculate wait time for this iteration
            long maxWait = wait;
            //if the passed in wait time is -1, means we should use the pool property value
            if (wait==-1) {
                maxWait = (getPoolProperties().getMaxWait()<=0)?Long.MAX_VALUE:getPoolProperties().getMaxWait();
            }
 
            long timetowait = Math.max(0, maxWait - (System.currentTimeMillis() - now));
            waitcount.incrementAndGet();
            try {
                //retrieve an existing connection
                con = idle.poll(timetowait, TimeUnit.MILLISECONDS);
            } catch (InterruptedException ex) {
                if (getPoolProperties().getPropagateInterruptState()) {
                    Thread.currentThread().interrupt();
                }
                SQLException sx = new SQLException("Pool wait interrupted.");
                sx.initCause(ex);
                throw sx;
            } finally {
                waitcount.decrementAndGet();
            }
            if (maxWait==0 && con == null) { //no wait, return one if we have one
                if (jmxPool!=null) {
                    jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.POOL_EMPTY, "Pool empty - no wait.");
                }
                throw new PoolExhaustedException("[" + Thread.currentThread().getName()+"] " +
                        "NoWait: Pool empty. Unable to fetch a connection, none available["+busy.size()+" in use].");
            }
            //we didn't get a connection, lets see if we timed out
            if (con == null) {
                if ((System.currentTimeMillis() - now) >= maxWait) {
                    if (jmxPool!=null) {
                        jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.POOL_EMPTY, "Pool empty - timeout.");
                    }
                    throw new PoolExhaustedException("[" + Thread.currentThread().getName()+"] " +
                        "Timeout: Pool empty. Unable to fetch a connection in " + (maxWait / 1000) +
                        " seconds, none available[size:"+size.get() +"; busy:"+busy.size()+"; idle:"+idle.size()+"; lastwait:"+timetowait+"].");
                } else {
                    //no timeout, lets try again
                    continue;
                }
            }
        } //while
    }

参数设置

属性名描述默认值
driverClassName用户名-
url密码-
username建立连接的url-
password数据库驱动的完整类名-
initialSize连接器启动时创建的初始连接数10
maxActive最大连接数,通常为常规访问的最大数据库并发数,建议根据后期jmx监控逐渐调优100
maxIdle最大空闲连接数,比较难把握的一个参数,许多连接池也已经移除了此属性(如Druid),访问峰值比较集中的系统如考勤可以设置小一点节省大部分时段的连接资源,过小也可能导致连接频繁创建关闭也会影响性能,建议一般系统不低于maxActive的50%100
minIdle最小连接数,一般与initialSize一致即可10
maxWait连接池中连接用完时,新的请求的等待时间,超时返回异常,单位毫秒默认30000
testWhileIdle连接进入空闲状态时是否经过空闲对象驱逐进程同时进行校验,推荐的校验方法,依赖validationQueryfalse
validationQuery在连接返回给调用者前用于校验连接是否有效的SQL语句,必须为一个SELECT语句,且至少有一行结果-
validationQueryTimeout连接验证的超时时间,单位秒,注:池本身并不会让查询超时,完全是依靠JDBC驱动来强制查询超时-
validationIntervalTomcatJDBC特有属性,检查连接可用性的时间间隔,防止testOnBorrow和testOnReturn为true时检查过于频繁,单位毫秒30000
timeBetweenEvictionRunsMillis空闲对象驱逐检查时间间隔,单位毫秒5000
minEvictableIdleTimeMillis连接被空闲对象驱逐进程驱逐前在池中保持空闲状态的最小时间,单位毫秒60000
defaultAutoCommit连接池所创建的连接默认自动提交状态(JDBC缺省值意思是默认不会调用setAutoCommit方法)JDBC缺省值
jmxEnabled是否利用 JMX 注册连接池true
jdbcInterceptorsTomcatJDBC特有属性, QueryTimeoutInterceptor(查询超时拦截器,属性queryTimeout,单位秒,默认1秒),SlowQueryReport(慢查询记录,属性threshold超时纪录阈值单位毫秒,默认1000),多个用拦截器用;分隔,示例:QueryTimeoutInterceptor(queryTimeout=5);SlowQueryReport(threshold=3000)注:当新语句创建时,自动调用Statement.setQueryTimeout(seconds)。池本身并不会让查询超时,完全是依靠JDBC驱动来强制查询超时,更详细的信息请查看官方文档-
testOnBorrow连接被调用时是否校验,依赖validationQuery,对性能有一定影响,不推荐使用false
testOnReturn连接返回到池中是时是否校验,依赖validationQuery,对性能有一定影响,不推荐使用false
removeAbandoned是否清除已经超过 removeAbandonedTimeout 设置的连接,可用于排查一些事务未提交的问题(正式环境谨慎使用,对性能有一定影响),不推荐使用,可用QueryTimeOut拦截器替代false
removeAbandonedTimeout清除无效连接的时间,单位秒 与removeAbandoned联合使用60
defaultReadOnly连接池创建的连接是否是否为只读,需要说明的是设置了true只是告诉数据库连接是只读,便于数据库做一些优化(例如不安排数据库锁),并非不能执行更新操作,只是对数据的一致性的保护并不强而已(这跟spring的只读事务类似)JDBC缺省

解决方法(参考)

基本上来说,大部分项目都需要跟数据库做交互,那么,数据库连接池的大小设置成多大合适呢?

连接数计算公式

连接数 = ((核心数 * 2) + 有效磁盘数)

核心数不应包含超线程(hyper thread),即使打开了超线程也是如此,如果热点数据全被缓存了,那么有效磁盘数实际是0,随着缓存命中率的下降,有效磁盘数也逐渐趋近于实际的磁盘数。另外需要注意,这一公式作用于SSD 的效果如何,尚未明了。

好了,按照这个公式,如果说你的服务器 CPU 是 4核 i7 的,连接池大小应该为 ((4*2)+1)=9。

取个整, 我们就设置为 10 吧。你这个行不行啊?10 也太小了吧!

你要是觉得不太行的话,可以跑个性能测试看看,我们可以保证,它能轻松支撑 3000 用户以 6000 TPS 的速率并发执行简单查询的场景。你还可以将连接池大小超过 10,那时,你会看到响应时长开始增加,TPS 开始下降。

比如说,你的系统同时混合了长事务和短事务,这时,根据上面的公式来计算就很难办了。正确的做法应该是创建两个连接池,一个服务于长事务,一个服务于"实时"查询,也就是短事务。

还有一种情况,比方说一个系统执行一个任务队列,业务上要求同一时间内只允许执行一定数量的任务,这时,我们就应该让并发任务数去适配连接池连接数,而不是连接数大小去适配并发任务数。

在配置tomcat数据库连接池时候,对配置的具体数值总是懵逼。这里给出具体建议。

首先上公式:
数据库连接池连接数 = ((核心数 * 2) + 有效磁盘数)
核心数如何得到?

linux 查看物理cpu的个数

 cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l

查看每个物理CPU中core的个数(即核数)

 cat /proc/cpuinfo| grep "cpu cores"| uniq

两数相乘即得到核心数。

有效磁盘数一般是一个。这样得到的数据库连接池个数是不是和自己猜想的小的很多?
下面进行理解一下。

单核 CPU同一时刻只能执行一个线程,然后操作系统切换上下文,CPU 核心快速调度,执行另一个线程的代码,不停反复,给我们造成了所有进程同时运行假象,其实这时候上下文切换的性能损耗很大。

其实,在一核 CPU 的机器上,顺序执行A和B永远比通过时间分片切换“同时”执行A和B要快,因为一旦线程的数量超过了 CPU 核心的数量,再增加线程数系统就只会更慢,而不是更快,因为这里上下文切换会耗费额外的性能。

Logo

鸿蒙生态一站式服务平台。

更多推荐