vlambda博客
学习文章列表

tomcat数据库连接池原理与配置详解


1 连接池原理

(本文虽然介绍的是tomcat连接池,但事实上各连接池的基本原理和配置是近似的,可以引申到其他连接池)Tomcat数据源连接池是对JDBC DataSource的一个具体实现。连接池的作用就是为了提高性能,将已经创建好的连接保存在池中,当有请求来时,直接使用已经创建好的连接对Server端进行访问。这样省略了创建连接和销毁连接的过程(TCP连接建立时的三.次握手和销毁时的四次握手),从而在性能上得到了提高。因此它本质是一个缓存系统,它缓存对象的是数据库连接Connection。

2 缓存系统的驱逐策略

对于一个缓存系统来说,它有个核心设计就是能够自动驱逐(evict)不再需要的缓存对象。说白了就是关注驱逐策略(驱逐哪些)和驱逐时机(何时驱逐)。

1.1 驱逐策略

一般evict机制有两种策略:time based和size based。time based是根据缓存对象的过期时间来判断,譬如规定存在超过1分钟的对象就需要从缓存中被剔除。size based是一旦缓存对象数目超出阈值系统就开始evict,否则会产生内存或者资源的泄漏。Tomcat连接池同时采用了这种两种策略。做evict的时候,既要判断数据库连接对象存在的时间,同时又要保证数据库连接的总数保持在阈值之内。

1.2 驱逐时机

一般evict时机也有两种:定时线程检查和存取时检查。定时线程检查即启动一个线程定时运行检查任务,任务逻辑是驱逐不再需要的缓存对象。存取时检查则是基于延迟删除的思路,没有额外的线程和判断开销,但是每次存取需要额外检查,并且空间不会及时释放。Tomcat连接池主要是基于前者,后者在归还连接returnConnection中有体现。

3 tomcat连接池概述

Tomcat数据源内部数据结构大致会分为两个集合:idle集合和busy集合,两个集合里放的都是数据库连接。Idle集合专门存放未被被应用使用的数据库连接,而busy集合指的是被应用正在使用的连接。一般情况下,当应用从数据源获取数据库连接的时候,一个数据库连接会从idle集合中去获取(borrowConnection),然后放入busy集合。当使用结束后再从busy集合归还到idle(returnConnection)。
数据库连接就在这两个集合之间移动。获取连接从idle集合移到busy集合叫做borrowConnection,会触发testOnBorrow的事件;反之,连接使用完毕释放是从busy集合移到idle集合叫做returnConnection,会触发testOnReturn的事件。用户可以利用这两个事件,对连接的有效性做检查,将失效连接剔除出数据源,这便是testOnValidation机制。

3.1 PoolCleaner定时清理器

在探讨驱逐时机时我们介绍了一种定时线程检查的方式,在tomcat连接池中它的实现便是PoolCleaner定时清理器。主要有3个功能:

3.1.1 checkAbandoned

检查busy queue中所有active连接是否要removeAbondone。首先要介绍何为abandoned机制,它是用于保证active的连接不会有泄漏而设计的,当一个active连接执行时间超过阈值,该机制就考虑这个连接可能有问题,根据一些后续判断决定是否close掉。

3.1.2 checkIdle

检查idle queue是否所有idle连接有长时间(minEvictableIdleTimeMillis)未使用的,如果有且idle剩余>=minIdle就close,这样就能保证有minIdle个连接随时可用

3.1.3 testAllIdle

遍历验证idle queue所有idle连接,是否通过VALIDATE_IDLE的validate验证机制。详细请参考3.2内容。

3.1.4 PoolCleaner开启条件

PoolCleaner并非一定开启,需要满足一些条件。是否开启由isPoolSweeperEnabled决定,该配置并非直接设置,而是通过其他配置综合判断:

boolean timer = getTimeBetweenEvictionRunsMillis()>0;
boolean result = timer && (isRemoveAbandoned() && getRemoveAbandonedTimeout()>0);
result = result || (timer && getSuspectTimeout()>0);
result = result || (timer && isTestWhileIdle() && getValidationQuery()!=null);
result = result || (timer && getMinEvictableIdleTimeMillis()>0);

满足4个条件任一条件即为true开启

  • 条件1,设置了timeBetweenEvictionRunsMillis>0;且开启了removeAbandoned;且设置 了removeAbandonedTimeout>0

  • 条件2,设置了timeBetweenEvictionRunsMillis>0;且设置了suspectTimeout>0

  • 条件3,设置了timeBetweenEvictionRunsMillis>0;且开启了testWhileIdle;且设置了 validationQuery

  • 条件4,设置了timeBetweenEvictionRunsMillis>0;且设置了minEvictableIdleTimeMillis>0

3.2 connection validation机制

在3.1.3 testAllIdle逻辑中,我们提到了connection validation机制。具体是:连接会执行validateQuery配置的sql(一般是极简的比如select 1),如果执行失败的连接就close。这个机制用于保证连接的健康度,某些场景下还有心跳的作用。tomcat连接池在好几个时机都可以开启该机制:

3.2.1 testOnConnection

在createConnection方法创建新连接时,如果配置testOnConnect=true或配置了initSQL,会进行validation校验存活。一般来说没有必要,因为刚创建的连接大概率是健康的。

3.2.2 testOnBorrow

在borrowConnection方法中,应用从连接池idle queue获取连接后,如果配置了testOnBorrow=true,会进行validation校验存活。

3.2.3 testOnReturn

在returnConnection方法中,应用使用完连接从busy queue归还到idle queue,如果配置testOnReturn=true,会进行validation校验存活。

3.2.4 testAllIdle

如果配置testWhileIdle=true,则在PoolCleaner中定时遍历idle queue所有idle连接,对每个连接进行validation校验存活。

4 配置详解

tomcat连接池有很多可供修改的配置,接下来结合上述原理和基础知识,对重要配置按相关性分组的形式介绍

4.1 initialSize & maxIdle & maxActive & maxWait

配置 默认值 说明
initialSize 10 池启动时创建的初始连接数
maxIdle 取自maxActive,即默认为100 池始终都应保留idle连接的最大数目
maxActive 100 池同时能分配的活跃连接(busy queue)的最大数目
maxWait 30000ms 没有可用连接时,连接池等待返回连接(存量或创建)的最长时间

连接池创建时立刻创建initialSize(10个)连接,如果使用侧不断需要连接会borrowConnection使用已有的10个,如果超过依旧有需求,则会不断createConnection创建连接,直到maxActive(100个)。当连接执行语句完成,会returnConnection归还连接到池中(本质是busy queue移到idle queue),在归还过程中,是否能归还成功还要判断maxIdle,如果idle queue已超过maxIdle,则该连接无法归还。但maxIdle是否起效又需要根据3.1.4提到的isPoolSweeperEnabled来决定,如果isPoolSweeperEnabled=false,即PoolCleaner不开启时maxIdle判断才生效。这个设计的意义是:如果PoolCleaner开启,则有3.1.2的checkIdle机制来保证idle queue的连接数量定时缩减而不至于太大;而PoolCleaner不开启时,连接池就利用maxIdle来限制idle queue的连接数量不至于太大。

4.2 timeBetweenEvictionRunsMillis & minEvictableIdleTimeMillis & minIdle

配置 默认值 说明
timeBetweenEvictionRunsMillis 5000ms PoolCleaner运行清理任务的间隔时间,即checkAbandoned、checkIdle、testAllIdle逻辑的间隔时间
minEvictableIdleTimeMillis 60000ms 对象在idle池中保持idle状态的最短时间,超过则驱逐关闭
minIdle 取自initialSize,即默认为10 池始终都应保留idle连接的最小数目,如果有idle连接无法通过validation可能关闭后低于该值

PoolCleaner每过timeBetweenEvictionRunsMillis,通过checkIdle逻辑检查一次连接池中空闲的连接,把空闲时间>=minEvictableIdleTimeMillis的连接驱逐断开,若idle queue中<=minIdle,则满足空闲时间也不会驱逐断开

4.3 removeAbandoned & removeAbandonedTimeout & AbandonWhenPercentageFull & logAbandoned & suspectTimeout

配置 默认值 说明
removeAbandoned false 用于配置是否开启removeAbandoned机制
removeAbandonedTimeout 60s busy池活跃连接执行语句允许的最长时间,如果超过则满足关闭条件之一
abandonWhenPercentageFull 0 若配置该项,则busy池活跃连接/maxActive的占比>=abandonWhenPercentageFull时,才进行removeAbandoned
suspectTimeout 0s 既是suspect的开关设置,也是超时设置,>0则代表开启suspect。suspect机制类似于removeAbandoned,是对busy池活跃连接执行语句时间的检查,但超时不会关闭连接,而是根据logAbandoned来打印警告。所以该值一般设置低于removeAbandonedTimeout。相当于先suspect警告记录,一段时间后再removeAbandoned
logAbandoned false 配置是否在发生removeAbandoned或suspect时打印日志和连接堆栈。由于记录堆栈,故在连接被borrow时会有额外开销

PoolCleaner每过timeBetweenEvictionRunsMillis,通过checkAbandoned逻辑,当isRemoveAbandoned=true,且设置removeAbandonedTimeout则会开启该机制。当连接执行语句超过removeAbandonedTimeout时,会再参考AbandonWhenPercentageFull配置,来决定是否将连接进行Abandoned,具体规则:
A. 如果abandonWhenPercentageFull不设置,则满足前2个条件即Abandoned
B. 如果abandonWhenPercentageFull设置,则还需要满足busy queue/maxActive>abandonWhenPercentageFull的比例时,才会Abandoned。这么设计的原因是连接即使满足timeout,但还得看busy线程池资源是否紧张,紧张才Abandoned
Abandoned操作就是release关闭连接,并且jmxPool.notify会发通知,再根据logAbandoned 决定是否打印abandoned日志。

4.4 testOnConnect & initSQL & testOnBorrow & testOnReturn & testWhileIdle & validationQuery & validationInterval

配置 默认值 说明
testOnConnect false 是否创建连接时进行validation校验
initSQL null 当连接第一次create创建时,运行的自定义查询
testOnBorrow false 从池获取连接时是否进行validation校验
testOnReturn false 归还连接时是否进行validation校验
testWhileIdle false 是否开启idle连接的定期validation校验
validationQuery null validation校验所执行的sql,越简单越好
validationInterval 30000ms 连接可能被多时机validation都校验,为防止短期重复校验,要间隔>=该值才再次validation校验

本节配置可参考3.2 connection validation机制中介绍场景使用,不再赘述

5 优化案例

下面配合实际案例来理解上述配置的优化,目前我司一些配置如下:

配置 当前值
testOnConnect 未设置,默认false
testOnBorrow true
testOnReturn false
testWhileIdle true
validationInterval 30000ms
validationQuery select 1

由于应用与数据库之间是同区域内网(大概率机房也相同),所以网络环境极佳,所以用于保证连接健康度的validation机制可以减少,以换取性能提升。具体到目前配置,testOnConnect、testOnReturn已设为false关闭,testOnBorrow也可以从true改为false,这意味着每次取连接时可以减少一次select 1的查询,整个查询量可以降低约一半(不考虑一次获取连接多次查询语句的话);关闭后影响并不大,因为testWhileIdle=true,PoolCleaner会定期进行testAllIdle的逻辑,保证idle连接的健康度,也能间接保证之后取到的连接是健康存活的。