Hikari使用配置与源码分析

一、SpringBoot2.x 默认连接池 HikariCP简单配置

现在国内用的最多的数据库连接池无疑是druid,因为它的监控功能实在太好用了,另外性能、稳定性、社区活跃度等各方面几乎没啥大的缺点。我们公司自然也是用的druid,这导致我一直没意识到springboot默认的连接池的存在。直到今天,我新建了一个springboot项目,导入jpa和web依赖包,配置好mysql地址,发现数据库连接竟然失败了。

我发现日志里有打印

MyHikariCP - Starting...

然后嘛,就是在启动这个连接池的过程中报错了。带着好奇心百度了下这个是啥玩意儿,一查才知道,这个是springboot2.x默认的数据库连接池,而springboot1.x默认的是tomcat连接池

于是我在网上找了份HikariCP的标准配置文件,只需改下数据库名和账号密码就能正常使用了。

## 数据库配置
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username = root
spring.datasource.password = root
##  Hikari 连接池配置 ------ 详细配置请访问:https://github.com/brettwooldridge/HikariCP
## 最小空闲连接数量
spring.datasource.hikari.minimum-idle=5
## 空闲连接存活最大时间,默认600000(10分钟)
spring.datasource.hikari.idle-timeout=180000
## 连接池最大连接数,默认是10
spring.datasource.hikari.maximum-pool-size=10
## 此属性控制从池返回的连接的默认自动提交行为,默认值:true
spring.datasource.hikari.auto-commit=true
## 连接池母子
spring.datasource.hikari.pool-name=MyHikariCP
## 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
spring.datasource.hikari.max-lifetime=1800000
## 数据库连接超时时间,默认30秒,即30000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1

二、HikariCP源码分析

HikariCP使用Javassist字节码操作库来实现动态代理,优化并精简了字节码,同时内部使用com.zaxxer.hikari.util.FastList代替了ArrayList,使用了更好的并发集合类com.zaxxer.hikari.util.ConcurrentBag,号称是目前最快的数据库连接池。

1. HikariPool和PoolEntry

HikariPool有三个内部类,其中一个是自定义的运行时异常,暂且不谈。另两个分别是HouseKeeper和PoolEntryCreater。HouseKeeper是一个Runnable线程,PoolEntryCreator是一个Callable执行单元。Hikari在自己的构造方法里使用了这两个线程类。

public HikariPool(){

  this.poolEntryCreator = new HikariPool.PoolEntryCreator((String)null);

  this.houseKeepingExecutorService = this.initializeHouseKeepingExecutorService();

  this.houseKeeperTask = this.houseKeepingExecutorService.scheduleWithFixedDelay(new HikariPool.HouseKeeper(), 100L, this.housekeepingPeriodMs, TimeUnit.MILLISECONDS);

}

先看下HikariPool的getConnection方法,里面调用了poolEntry.createProxyConnection(),poolEntry是poolEntryCreator创造出来放到ConcurrentBag<PoolEntry>里的,这里取出来。

再来看createProxyConnection

Connection createProxyConnection(ProxyLeakTask leakTask, long now) {
        return ProxyFactory.getProxyConnection(this, this.connection, this.openStatements, leakTask, now, this.isReadOnly, this.isAutoCommit);
}

终于走到ProxyFactory这里,具体看下面有关hikari工厂和代理的讲解。

2. ProxyFactory

代码简化下大致是这样:

public final class ProxyFactory{
      static ProxyConnection getProxyConnection(){return new HikariProxyConnection()}; 
static Statement getProxyStatement(){return new
HikariProxyStatement()};
static CallableStatement getProxyCallableStatement(){return new HikariProxyCallableStatement()};
static PreparedStatement getProxyPreparedStatement(){return new HikariProxyPreparedStatement()};
static ResultSet getProxyResultSet(){return new HikariProxyResultSet()};
static DatabaseMetaData getProxyDatabaseMetaData(){return new HikariProxyDatabaseMetaData()};
}

因为只有一个具体工厂类,里面的工厂方法也都是静态的,所以这是一个典型的简单工厂模式。

ProxyFactory生产的是Hikari有关数据库操作相关的代理类,被代理的类是java.sql下面数据库操作的原生类。

3. hikari中的动态代理

我们用ProxyConnection这个代理类来举例,这里只贴出部分代码

public abstract class ProxyConnection implements Connection {

protected ProxyConnection(Connection connection) {
        this.delegate = connection;
    }
}

可以看到ProxyConnection实现了共用主题接口Connection,并且通过构造方法持有另一个Connection实际对象的引用。这里另一个Connection实际对方就是被代理的类。

这里使用了JavassistProxyFaxctory动态代理,是预先加载字节码的,涉及到类加载机制,我还没看懂,以后慢慢看。

4. fastlist对比arraylist

总结:因为fastlist仅供内部使用,所以减少了很多安全校验。另外fastlist因为确认马上就会使用,直接初始化长度32的数组,扩容也改成2倍再扩容,减少了扩容次数。

这位大佬讲的很全面,从构造函数、扩容、插入、删除、取值、迭代器各方面做了对比。

读HikariCP源码学Java(二)—— 因地制宜的改装版ArrayList:FastList - 缪若尘 - 博客园 (cnblogs.com)

5. ConcurrentBag

这个是借鉴了c#中的设计,主要有如下特点

  1. A lock-free design
  2. ThreadLocal caching
  3. Queue-stealing
  4. Direct hand-off optimizations

 

最后,推荐下这个大佬写的追光者系列Hikari三连,感觉写得很好,我自己也还没看完,mark一下有空再看。

【追光者系列】HikariCP源码分析之字节码修改类库Javassist委托实现动态代理 - 云+社区 - 腾讯云 (tencent.com)

三、简单看下druid源码

大概了看了下druid没有类似hikari里那么明显的pool类,它存放连接的容器叫DruidConnectionHolder。druid核心代码主要在DuidDataSource,这个类是简单工厂DruidDataSourceFactory的产品。那么着重看下DruidDataSource,它的父类实现了java.sql.DataSource接口,所以它本质也是DataSource的一个实现类。

结合druid使用时的配置来看,会更好理解一点   

    initialSize: 5
minIdle:
5 # 初始化大小,最小,最大 maxActive: 20 maxWait: 60000 # 配置获取连接等待超时的时间 timeBetweenEvictionRunsMillis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 maxPoolPreparedStatementPerConnectionSiz: 20 filters: stat,wall,log4j connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 通过connectProperties属性来打开mergeSql功能;慢SQL记录

1. 先看DruidDataSource的构造方法

默认是非公平锁(效率高),也可以使用有参构造函数传入false设置为公平锁。构造函数中创建计算器值为2的全局CountDownLatch。

2. 然后看DruidDataSource对外提供的入口函数getConnection(long maxWaitMillis)

这个参数表示连接不使用后最大等待时间。函数内部首先调用init()函数进行了DruidDataSource的初始化

1)异步初始化

init中判断如果asyncInit为true,也就是异步初始化,则根据使用for循环去创建initialSize数量的CreateConnectionTask线程,并将线程提交到Future中去运行。

2)同步初始化

如果asyncInit为false,则使用while语句判断poolingCount是否小于initialSize,如果小于,则直接调用父类的createPhysicalConnection()创建一个连接,并放入holder容器。

3)创建守护线程

紧接着init()函数会创建LogStatsThread、CreateConnectionThread、DestroyConnectionThread这三个守护线程,其中LogStatsThread不会使用CountdownLatch。这时候调用全局CountDownLatch的await函数,等待上面几个守护进程创建的核心连接创建完成。

3. DruidDataSource有5个内部类

LogStatsThread  继承Thread类,设置为守护线程,功能是打印日志。

CreateConnectionThread  继承Thread类,设置为守护线程。在run()方法的第一步会调用countDown(),只会执行一次。下面会在while死循环中判断是否需要创建新连接,如果满足条件会调用createPhysicalConnection()。

DestroyConnectionThread  继承Thread类,设置为守护线程。在run()方法的第一步会调用countDown(),只会执行一次。下面会在while死循环中判断是否需要销毁连接,如果满足条件,调用destoryTask的run()方法。

CreateConnectionTask   实现Runnable接口,核心方法runInternal() ,如果没有等待使用的闲置连接(这个判断过程用ReetrantLock保证同步安全),则创建连接。创建连接的方法createPhysicalConnection()是DruidDataSource的父类中定义的方法。另外这里面有一个fastfail快速失败机制以后有空再研究。

DestroyTask   实现Runnable接口,run方法中移除超时的连接。

4. DruidConnectionHolder

5. druid中的过滤器链

用来实现监控、日志、防sql注入,分别对应参数 filters: stat,log4j,wall

6. DruidDataSourceC3P0Adapter

这个适配器用来适配c3p0的连接,具体还没细看。hikari中貌似没看到这种适配设计。

7. ConnectionProxyImpl

这个是druid中的代理类,被代理的是connection各个开发商具体的实现类。这个我是看其它源码分析文章看到的,先记录在这儿,后面继续深入看druid源码的时候再慢慢看。

 

posted @ 2022-03-18 01:57  方山客  阅读(2444)  评论(0编辑  收藏  举报