##零、类图&流程预览
本文会通过getConnection
作为入口,探索在druid
里,一个连接的生命周期。大体流程被划分成了以下几个主流程:
##主流程1:获取连接流程
首先会调用init
进行连接池的初始化,然后运行责任链上的每一个filter
,最终执行getConnectionDirect
获取真正的连接对象,如果开启了testOnBorrow
,则每次都会去测试连接是否可用(这也是官方不建议设置testOnBorrow
为true的原因,影响性能,这里的测试是指测试mysql服务端的长连接是否断开,一般mysql
服务端长连保活时间是8h
,被使用一次则刷新一次使用时间,若一个连接距离上次被使用超过了保活时间,那么再次使用时将无法与mysql
服务端通信)。
如果
testOnBorrow
没有被置为true,则会进行testWhileIdle
的检查(这一项官方建议设置为true
,缺省值也是true
),检查时会判断当前连接对象距离上次被使用的时间是否超过规定检查的时间,若超过,则进行检查一次,这个检查时间通过timeBetweenEvictionRunsMillis
来控制,默认60s
。
每个连接对象会记录下上次被使用的时间,用当前时间减去上一次的使用时间得出闲置时间,闲置时间再跟timeBetweenEvictionRunsMillis
比较,超过这个时间就做一次连接可用性检查,这个相比testOnBorrow
每次都检查来说,性能会提升很多,用的时候无需关注该值,因为缺省值是true
,经测试如果将该值设置为false
,testOnBorro
w也设置为false
,数据库服务端长连保活时间改为60s
,60s
内不使用连接,超过60s
后使用将会报连接错误。
若使用testConnectionInternal
方法测试长连接结果为false
,则证明该连接已被服务端断开或者有其他的网络原因导致该连接不可用,则会触发discardConnection
进行连接回收(对应流程1.4,因为丢弃了一个连接,因此该方法会唤醒主流程3进行检查是否需要新建连接)。整个流程运行在一个死循环内,直到取到可用连接或者超过重试上限报错退出(在连接没有超过连接池上限的话,最多重试一次(重试次数默认重试1次,可以通过notFullTimeoutRetryCount
属性来控制),所以取连接这里一旦发生等待,在连接池没有满的情况下,最大等待2 × maxWait
的时间 ←这个有待验证)。
##特别说明①
为了保证性能,不建议将testOnBorrow设置为true,或者说牵扯到长连接可用检测的那几项配置使用druid默认的配置就可以保证性能是最好的,如上所说,默认长连接检查是60s一次,所以不启用testOnBorrow的情况下要想保证万无一失,自己要确认下所连的那个mysql服务端的长连接保活时间(虽然默认是8h,但是dba可能给测试环境设置的时间远小于这个时间,所以如果这个时间小于60s,就需要手动设置timeBetweenEvictionRunsMillis了,如果mysql服务端长连接时间是8h或者更长,则用默认值即可。
##特别说明②
为了防止不必要的扩容,在mysql服务端长连接够用的情况下,对于一些qps较高的服务、网关业务,建议把池子的最小闲置连接数minIdle和最大连接数maxActive设置成一样的,且按照需要调大,且开启keepAlive进行连接活性检查(参考流程4.1),这样就不会后期发生动态新建连接的情况(建连还是个比较重的操作,所以不如一开始就申请好所有需要的连接,个人意见,仅供参考),但是像管理后台这种,长期qps非常低,但是有的时候需要用管理后台做一些巨大的操作(比如导数据什么的)导致需要的连接暴增,且管理后台不会特别要求性能,就适合将minIdle的值设置的比maxActive小,这样不会造成不必要的连接浪费,也不会在需要暴增连接的时候无法动态扩增连接。
##主流程2:初始化连接池
通过上面的流程图可以看到,在获取一个连接的时候首先会检查连接池是否已经初始化完毕(通过inited来控制,bool类型,未初始化为flase,初始化完毕为true,这个判断过程在init方法内完成),若没有初始化,则调用init进行初始化(图主流程1中的紫色部分),下面来看看init方法里又做了哪些操作:
可以看到,实例化的时候会初始化全局的重入锁
lock
,在初始化过程中包括后续的连接池操作都会利用该锁保证线程安全,初始化连接池的时候首先会进行双重检查是否已经初始化过,若没有,则进行连接池的初始化,这时候还会通过SPI机制额外加载责任链上的filter。
但是这类filter需要在类上加上@AutoLoad注解。然