HikariCP为啥这么快

数据库连接池原理

在系统初始化的时候,在内存中开辟一片空间,将一定数量的数据库连接作为对象存储在对象池里,并对外提供数据库连接的获取和归还方法。用户访问数据库,并不是建立一个新的连接,而是从数据库连接池中取出一个已有的空闲连接对象;使用完毕归还后的连接也不会马上关闭,而是由数据库连接池统一管理回收,为下一次借用做好准备。如果由于高并发请求导致数据库连接池的连接被借用完毕,其它线程就会等待,直到有连接被归还。整个过程中,连接并不会关闭,而是源源不断地循环使用,有借有还。

常见数据库连接池

  • C3P0:实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象

  • DBCP: Apache下独立的数据库连接池组件,由于Apache的缘故,它可能是使用最多的开源数据库连接池

  • BoneCP: 在c3p0和DBCP存在的时代,BoneCP的出现就是为了追求极致,并且提供了完善的基准测试

  • Druid: 阿里出品,是阿里巴巴唯一使用的数据库连接池,阿里云DRDS和阿里TDDL都采用了Druid,可支持”双十一”等最严苛的使用场景,并且提供了强大的监控功能,在国内有不少用户。

  • HikariCP: HiKariCP是数据库连接池的一个后起之秀,号称性能最好,可以完美地PK掉其他连接池,Springboot 2.0选择HikariCP作为默认数据库连接池

有一个争论是HikariCP与Druid相比哪个更好,对此Druid作者温少是直接上场对过线的,感兴趣的可以参考:

https://github.com/brettwooldridge/HikariCP/issues/232

avatar

HikariCP为什么这么快

在HikariCP官网(https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole)详细介绍了HikariCP所做的优化:

  • 优化并精简字节码、优化代码和拦截器
  • 使用FastList替代ArrayList
  • 有更好的并发集合类实现ConcurrentBag
  • 其它针对BoneCP缺陷的优化,比如对耗时超过一个CPU时间片的方法调用的研究

接下来将探究FastList和ConcurrentBag的实现

FastList

HikariCP重现设计了一个List接口实现类,用以替换ArrayList。FastList是List接口的精简实现,只实现了接口中必要的几个方法。

jdk中的ArrayList:

1
2
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

HikariCP中的FastList:

1
public final class FastList<T> implements List<T>, RandomAccess, Serializable

可以看到FastList并没有继承AbstractList

ArrayList的get方法:

1
2
3
4
5
6
7
8
9
10
public E get(int index) {
rangeCheck(index);

return elementData(index);
}

private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

FastList的get方法:

1
2
3
public T get(int index)  {
return elementData[index];
}

可以看出FastList的get方法取消了rangeCheck,在一定程度上追求了极致。

ArrayList的remove(Object)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)//从头到尾遍历
if (elementData[index] == null) {
fastRemove(index);//从头到尾移除
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

与ArrayList相反,FastList选择从数组的尾部开始遍历(JDBC编程中的常见模式是在使用后立即关闭Statement,或者以打开的相反顺序关闭Statement,可以理解为同一个Connection创建了多个Statement时,后打开的Statement会先关闭),因而更加高效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public boolean remove(Object element) {
for (int index = size - 1; index >= 0; index--) {//从尾部遍历
if (element == elementData[index]) {
final int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
elementData[--size] = null;
return true;
}
}

return false;
}

ConcurrentBag

参考资料

HikariCP数据库连接池实战