相信很多看过小蜜蜂(BeeCP)连接池的性能对比图的网友都会觉得惊讶不已,这比被号称史上最快的光连接池还快啊,简直不可思议!想知道它性能强悍的原因吗?今天就为大家解开这个谜团。
连接借用
故事还是要从连接池本身说起,连接池技术是一门古老的IT技术,其本质并不复杂,其工作原理类似图书馆,只不过它借出/回收的是连接对象,在连接池类内部一般至少有两条列表,第一条存放连接对象(类似书架),第二条存放等待者线程(或隐式), 如果连接都被借光了,借用者只能排队等待其他借用者归还,等待过程中,某些等待者可能会因为时间达到最大允许时刻点,则会退出等待,这种离开现象叫等待超时。
传递与队列
借用在使用完毕后将被关闭(实际关闭只是代理对象而已),背后自动触发连接池对连接的回收,如果在池中存在等待者,那么连接池将回收过来的连接传递给等待者,这是一件很有意思的事情,各连接池的性能的差异,很大一部分是由传递的速度导致的(谁的传递效率高,谁的性能更强),传递的方式通常是借助于同步队列的管道推拉方式完成的,比如Tomcat-JDBC使用的是闲/忙两条队列,光连接池采用的是同步队列(SynchronousQueue)代码类似:
归还线程A:queue.offer(object);
等待线程B:queue.poll(time);
对于线程 B在有效时间范围内进行等待,无论采用LinkedBlockingQueue还是采SynchronousQueue,背后直接或间接使用了等待链技术,比如SynchronousQueue就比较直白式,自定义了节点链,比如LinkedBlockingQueue则是通过Lock对象来完成的,其实Lock对象的本质就是等待链,有兴趣的同学可以翻看一下源码,AbstractQueuedSynchronizer对象是Lock的源头,它内部就包含一条CAS节点链,看类上的介绍就清楚了,不用细看代码,一般同学只是使用而已,又不搞JDK代码开发, 没有必要研究太深。
传递效率分析
对连接池而言,传递的效率直接决定其性能的高低,如果存在大量的并发的时候,其实使用Offer/Poll的方式,性能并不会很理想,以SynchronousQueue为例说明:A为归还线程,B为等待者线程,C为刚进入的借用者线程,A在归还的时候,首先将连接设置为闲置状态,并通过队列传递给B, 此时C从连接数组中( 或CopyOnWriteArrayList)搜索的时也发现这个刚释放的连接,因此B与C同时通过才CAS的方式抢占这个连接,假如B抢占失败,又未超时,怎么办呢?继续Poll(本质上是再次加入等待链)有可能存在反复出入等待链的情况,大家可以想想,假如不少线程发生这种情况,那么势必造成性能损耗,甚至出现早加入等待者比后来加入的等待者需要更多时间才获得连接,为啥呢?每次失败后,都是重新加入等待链的末端,从而违背了FIFO原则,理想方式是:我先加入等待,那么我应该比后等待者先获得对象。 (这就是某池在并发比较多的情况,部分请求出现时间夸特别度大的根本原因)
小蜜蜂池如何传递
小蜜蜂连接池采用并发队(ConcurrentLinkedQueue)队列完成等待/传递动作,队列中的等待者在未获得有效的连接前,是不会离开队列的;回收后的连接总是从队列首位置进行传递,因此保正FIFO公平性原则( 按队列排队次序获得传递的机会 ),这个处理方式大大减少不要的队列中移动,从而大大提升传递(Transfer)的效率,这个方式是连接池领域中一次技术创新,是小蜜蜂连接池与其他连接池区分开来的重要标志,我将它称之为小蜜蜂队列技术。
小蜜蜂连接池在处理Transfer的方式,采用两种业务模式:公平与竞争; 在公平模式下,释放的连接在不改变状态的前提下(状态为使用 中:USING)进行传递,在这个模式下,上述的C线程通过cas的方式是无法持有的,从而保证B能顺利获得此连接;在竞争模式下,B与C抢占这个连接,如果B成功取得后,再检测是有一个有效的连接后,才会离开等待队列;如果B抢占失败后,在未超时间的情况下,那么B是不会离开队列,排列B的后面有B2,B3.....
除了拥有这个创新式技术,小蜜蜂连接池还有其他一些亮点,下面一一罗列。
1:合理的线程控制
学过计算机的同学都知道,CPU是采用分时执行运行时线程的,可以想象一下如果有大量并发的线程同时运行,那么CPU需要再这些线程之间切换运行,势必会造成较大的损耗,小蜜蜂连接池使用信号量(Semaphore:可形象化比喻为一个具有多通道的门)的方式控制并发借用线程数,从而减少CPU一些不必要的切换。
以4核CPU为例,连接的最大个数为8个,将并发控制为4,保证同一时刻4个线程可以进入getConnection方法的信号量内部,期望的理想状态是:4个连接处于待借,4个连接处于使用中,使每个核覆盖2个线程,这样既不给CPU造成很大压力,又能充分利用CPU
2:合理利用队列与缓存
A: 小蜜蜂连接池中,在两处可能发生等待现象:第一处是等待信号量的许可(信号量背后就是等待链),第二处等待是等待传递(Transfer),处理的方式是:大队列等待,小队列交换,意思是说:在大量并发的情况,信号量的等待链中持有更多的更多等待线程,让小规模的并发队列来完成第二处等待和传递。
B: 小蜜蜂连接池中采用单连接缓存,并在到达信号量位置之前,自行通过CAS的方式尝试抢占曾经使用过的连接(如果多缓存,还不如直接从列表获取呢)
3: 精准控制超时
借用线程进入借用方法(getConnection)后,预先计算超时时刻点(既借用者线程在迟内活动的时刻点不能超过该点),超时时间默认是8秒,意为:8秒以内未取得连接,借用者线程自动离开池方法,这个时间是覆盖池内的两处可能出现等待的时间之和。
4:连接的安全保证
A: JDBC物理队对象通过代理分装,不允许调用者直接获取
B: 连接在使用完毕后,重置处理,避免脏连接归还到池中(如果出现,关闭老的,则补充一个新)
C: 连接有效性判断检查(存活性,闲置超时,持有不用超时)
D: 安全性关闭(如果Close方法被多个并发线程关闭,只能有一个关闭成功)
E: 三连级检查(Result检查自己的close,还要检查Statement是否关闭,再上一级Connection是否关闭)确保对象的安全性
5:代码极致优化
A: PSCache缓存优化(包括hashKey的计算)
B: 去掉一些get/set方法,直接使用对象的属性值
C: 一些CasUpdator不是放对象内部,减少方法调用次数
总之来说,小蜜蜂连接池是一款优秀的开元作品,希望推荐给更多公司和个人使用,同时欢迎各位网友提问,或建议,或质疑. 项目工程请访问: [https://github.com/Chris2018998/BeeCP]
来源:oschina
链接:https://my.oschina.net/u/3918073/blog/4310142