TianEx / Blog

My blog ~
1 stars 0 forks source link

Druid详解 #19

Open TianEx opened 5 years ago

TianEx commented 5 years ago

初始化

每次获取connection都会调用init,内部使用inited标识DataSource是否已经初始化:

  1. 初始化filter,这些filter可以嵌入各个环节,包括创建/销毁连接、提交/回滚事务等
  2. 加载数据库Driver,并根据不同的数据库,实例化ExceptionSorter、ValidConnectionChecker(获取/回收连接时用到)
  3. 初始化JdbcDataSourceStat用于监控
  4. 初始化connections、evictConnections、keepAliveConnections数组,分别用于存放可被获取的连接池(未被取走,内部维护一个poolingCount值代表队列中剩余的可用的连接)、待清理的连接池(失效过期的连接暂存于此数组)、存活连接的pool(销毁线程会检测连接,如果存活暂放于此然后统一放入connections)
  5. 初始化initialSize个connection
  6. 开启LogStatsThread线程,用于定期打印DruidDataSource的一些数据,开启需设置timeBetweenLogStatsMillis指定打印时间周期,由于log步骤需要获取lock,所以时间不要设得太短
  7. 创建CreateConnectionThread线程,内部默认使用一个线程异步创建连接,可以将keepAlive设为true并指定createScheduler线程池开启多个线程创建连接
  8. 创建DestroyConnectionThread线程,定期扫描连接池内过期的连接,如果想对连接池外面正在使用的连接也进行清理的话,需要指定removeAbandoned为true,清理线程会判断连接是否正在使用,是否超过了清理时间而进行清理

PS:获取程序的调用栈,标注由哪个函数调用的init方法 从SPI中加载Filter,如果前面加载的filter不存在则还需要进行初始化 initedLatch用于等待之前的线程初始化完成 如果keepAlive为true并且createScheduler不为空,则初始化minIdle个线程用于创建连接

获取连接

  1. init
  2. 如果存在filter,则使用filter创建连接
  3. 调用getConnectionInternal获取经过各种包装的connection,这个是获取连接的主要逻辑
  4. 如果testOnBorrow为true,则对连接进行校检,校检失败则进行清理并重新进入循环
  5. 如果testWhileIdle为true,则距离active时间超过timeBetweenEvictionRunsMillis则进行check动作
  6. 如果removeAbandoned为true,则会把当前线程的stackTrace保存起来,连接存放在activeConnections中,清理线程会定期对其进行处理 pollLast -> 如果连接池内没有连接,则调用empty#signal,通知createThread创建连接,并且等待指定时间 taskLast -> 与poll的不同在于只是存在等待时间的差异 CreateConnectionThread进入await:
  7. 连接池内的连接个数大于等待的线程数量
  8. 如果池内、外的连接数大于maxActive,也进入await

DestroyConnectionThread线程会定期执行一次清理动作,可以指定timeBetweenEvictionRunsMillis控制清理的频率, 主要逻辑在DestroyTask,首先会执行shrink对过期时间进行处理,然后根据removeAbandoned的值判断是否需要清理abandoned连接,shrink只针对连接池的连接进行清理,而removeAbandoned会对从连接池外的连接进行清理。

shrink只会清理连接池内的连接,有几个参数需要注意下: phyTimeoutMillis:连接最大存活时间,默认是-1(不限制物理连接时间),从创建连接开始计算如果超过该时间则会被清理 keepAlive:默认是false,标记连接池内的连接是否需要保持存活,如果设为true的话,则会每次清理的时候,都会调用validateConnection,针对mysql而言就会往db发ping维持连接;这和前面提到的testOnBorrow、testWhileIdle类似 lastActiveTimeMillis:上次active的时间,在连接被recycle或者清理线程在清理时会重新标记该时间,需指定keepAlive为true minEvictableIdleTimeMillis:默认1800000ms,如果上次active时间lastActiveTimeMillis至当前时刻,如果小于该时间,则不进行清理 maxEvictableIdleTimeMillis:默认25200000ms,如果超过了maxEvictableIdleTimeMillis则该连接会被清理掉

shrink对时间的处理比较复杂,下面基于checkTime为true的情况进行分析: 如果是指定了phyTimeoutMillis,并且创建时间大于该值,则直接回收 如果上次active时间小于minEvictableIdleTimeMillis则不进行处理 如果上次active时间小于minEvictableIdleTimeMillis则清理掉checkCount个连接(需要维持在minIdle个连接) 如果超过了maxEvictableIdleTimeMillis直接清理 如果介于minEvictableIdleTimeMillis和maxEvictableIdleTimeMillis之间,并且keepAlive是true,则需要keepAlive

锁 -> 多线程create/destroy/get操作连接,所以操作线程需要获取锁才可以对内部数据进行操作 如果使用公平锁,应用调用getConnection是根据请求的先后顺序依次获取connection,非公平锁则允许抢占 notEmpty -> 如果获取连接的线程发现pool空,一方面会signal empty,另一方面自己会调用notEmpty#await进行等待,由 CreateConnectionThread唤醒,或其他线程释放连接时唤醒 empty -> CreateConnectionThread只有在连接池不够用的情况下才会创建,否则调用empty#await挂起线程