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连接的健康度,也能间接保证之后取到的连接是健康存活的。