前言
紧接上一章,这一章看看HikariCP连接池启动的流程。HikariDatasource构造时,HikariCP连接池随即启动。
HikariConfig config = new HikariConfig();
config.setUsername(username);
config.setPassword(password);
config.setJdbcUrl(jdbcUrl);
config.setDriverClassName(driver);
// 连接池启动
this.dataSource = new HikariDataSource(config);
一、HikariConfig构造
HikariDataSource继承HikariConfig,所以先执行父类构造。HikariConfig的无参构造,执行默认配置赋值。
public HikariConfig() {
// 默认最小连接数 -1,之后HikariDataSource构造会改成10
minIdle = -1;
// 默认最大连接数 -1,之后HikariDataSource构造会改成10
maxPoolSize = -1;
// 默认连接最大时长30分钟
maxLifetime = MINUTES.toMillis(30);
// 默认获取连接超时时间30秒
connectionTimeout = SECONDS.toMillis(30);
// 默认校验连接超时时长5秒
validationTimeout = SECONDS.toMillis(5);
// 默认空闲超时时间10秒
idleTimeout = MINUTES.toMillis(10);
// 默认需要初始化校验,并且超时时长1毫秒
initializationFailTimeout = 1;
// 默认自动提交
isAutoCommit = true;
// 可以通过设置系统参数,加载外部配置
String systemProp = System.getProperty("hikaricp.configurationFile");
if (systemProp != null) {
loadProperties(systemProp);
}
}
二、HikariDataSource构造
HikariDataSource构造方法有两个:无参构造,延迟加载连接池(getConnection触发连接池加载)。建议使用有参构造,效率较高(见第一章)。
public class HikariDataSource extends HikariConfig implements DataSource, Closeable {
private final AtomicBoolean isShutdown = new AtomicBoolean();
private final HikariPool fastPathPool;
private volatile HikariPool pool;
public HikariDataSource() {
super();
fastPathPool = null;
}
public HikariDataSource(HikariConfig configuration) {
// 校验配置,设置默认值
configuration.validate();
// 拷贝入参配置到自己
configuration.copyStateTo(this);
// 创建连接池
pool = fastPathPool = new HikariPool(this);
// 标记配置已经在使用
this.seal();
}
}
HikariConfig#validate
校验配置合法,设置默认值。
public void validate() {
// 生成Pool名 HikariPool-1
// 从系统变量(com.zaxxer.hikari.pool_number)中获取,每次自增
if (poolName == null) {
poolName = generatePoolName();
}
else if (isRegisterMbeans && poolName.contains(":")) {
throw new IllegalArgumentException("poolName cannot contain ':' when used with JMX");
}
catalog = getNullIfEmpty(catalog);
connectionInitSql = getNullIfEmpty(connectionInitSql);
connectionTestQuery = getNullIfEmpty(connectionTestQuery);
transactionIsolationName = getNullIfEmpty(transactionIsolationName);
dataSourceClassName = getNullIfEmpty(dataSourceClassName);
dataSourceJndiName = getNullIfEmpty(dataSourceJndiName);
driverClassName = getNullIfEmpty(driverClassName);
jdbcUrl = getNullIfEmpty(jdbcUrl);
// ...校验数据源相关配置是否存在,比如jdbcUrl
// 校验连接池数字类型相关的配置是否合理,设置默认值
validateNumerics();
// 打印配置信息
if (LOGGER.isDebugEnabled() || unitTest) {
logConfiguration();
}
}
private void validateNumerics() {
// 如果maxLifetime<30秒,设置为30分钟
if (maxLifetime != 0 && maxLifetime < SECONDS.toMillis(30)) {
maxLifetime = MAX_LIFETIME;
}
// 连接泄露检测时长 < 2s 或 > maxLifetime,设置为0关闭检测
if (leakDetectionThreshold > 0 && !unitTest) {
if (leakDetectionThreshold < SECONDS.toMillis(2) || (leakDetectionThreshold > maxLifetime && maxLifetime > 0)) {
leakDetectionThreshold = 0;
}
}
// 如果connectionTimeout<250ms,设置为30秒
if (connectionTimeout < 250) {
connectionTimeout = CONNECTION_TIMEOUT;
}
// 如果validationTimeout<250ms,设置为5秒
if (validationTimeout < 250) {
validationTimeout = VALIDATION_TIMEOUT;
}
// 之前HikariConfig默认设置为-1了,这里改为10
if (maxPoolSize < 1) {
maxPoolSize = DEFAULT_POOL_SIZE;
}
// 之前HikariConfig默认设置为-1了,这里改为maxPoolSize
if (minIdle < 0 || minIdle > maxPoolSize) {
minIdle = maxPoolSize;
}
// 空闲时长默认逻辑
if (idleTimeout + SECONDS.toMillis(1) > maxLifetime && maxLifetime > 0 && minIdle < maxPoolSize) {
idleTimeout = 0;
}
// 空闲时长最小10秒
else if (idleTimeout != 0 && idleTimeout < SECONDS.toMillis(10) && minIdle < maxPoolSize) {
idleTimeout = IDLE_TIMEOUT;
}
}
三、PoolBase构造
HikariPool继承PoolBase,先执行PoolBase构造方法。在initializeDataSource
方法中创建了DriverDataSource
,DriverDataSource
是真正负责获取Connection的DataSource。
PoolBase(final HikariConfig config) {
// 省略其他成员变量赋值操作,基本是从config入参拷贝过来
// 如果connectionTestQuery为空,使用ping检测与数据库连通性
this.isUseJdbc4Validation = config.getConnectionTestQuery() == null;
// 创建DriverDataSource
initializeDataSource();
}
private void initializeDataSource() {
final String jdbcUrl = config.getJdbcUrl();
final String username = config.getUsername();
final String password = config.getPassword();
final String dsClassName = config.getDataSourceClassName();
final String driverClassName = config.getDriverClassName();
final Properties dataSourceProperties = config.getDataSourceProperties();
// 省略各种判空逻辑,最终是创建了DriverDataSource负责实际获取连接
DataSource ds = new DriverDataSource(jdbcUrl, driverClassName, dataSourceProperties, username, password);
if (ds != null) {
setLoginTimeout(ds);
createNetworkTimeoutExecutor(ds, dsClassName, jdbcUrl);
}
this.dataSource = ds;
}
四、HikariPool构造
HikariPool构造创建了支持连接池运行所必须的各种对象实例。
public HikariPool(final HikariConfig config) {
// PoolBase构造
super(config);
// 创建ConcurrentBag
this.connectionBag = new ConcurrentBag<>(this);
// 如果连接池支持挂起
// 设置suspendResumeLock=SuspendResumeLock
// 否则使用SuspendResumeLock.FAUX_LOCK
this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
// 创建housekeeper线程池
this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
// 初始化校验 快速失败
checkFailFast();
// 用户自定义线程工厂,一般是空
ThreadFactory threadFactory = config.getThreadFactory();
// 最大连接数
final int maxPoolSize = config.getMaximumPoolSize();
// 添加连接线程池里的阻塞队列
LinkedBlockingQueue<Runnable> addConnectionQueue = new LinkedBlockingQueue<>(maxPoolSize);
// 对于上面的阻塞队列保存一个视图,只能查询不能修改
this.addConnectionQueueReadOnlyView = unmodifiableCollection(addConnectionQueue);
// 创建添加连接线程池
this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardOldestPolicy());
// 创建关闭连接线程池
this.closeConnectionExecutor = createThreadPoolExecutor(maxPoolSize, poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
// 创建连接泄露检测Task的工厂
this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
// 开启HouseKeeper定时任务
this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);
// 如果com.zaxxer.hikari.blockUntilFilled=true,且支持初始化校验
if (Boolean.getBoolean("com.zaxxer.hikari.blockUntilFilled") && config.getInitializationFailTimeout() > 1) {
// 扩大添加连接线程池到16
addConnectionExecutor.setCorePoolSize(Math.min(16, Runtime.getRuntime().availableProcessors()));
addConnectionExecutor.setMaximumPoolSize(Math.min(16, Runtime.getRuntime().availableProcessors()));
// 等待连接池内总连接数达到最小连接数(默认10)
final long startTime = currentTime();
while (elapsedMillis(startTime) < config.getInitializationFailTimeout() && getTotalConnections() < config.getMinimumIdle()) {
quietlySleep(MILLISECONDS.toMillis(100));
}
// 恢复添加连接线程池到1
addConnectionExecutor.setCorePoolSize(1);
addConnectionExecutor.setMaximumPoolSize(1);
}
}
SuspendResumeLock
SuspendResumeLock
专门用于连接池挂起和恢复,利用的是JUC的Semaphore。
- acquire:每次getConnection都会调用这个方法,如果当前连接池处于挂起状态,获取不到信号量将阻塞等待。
- release:释放信号量。
- suspend:阻塞等待获取所有信号量。
- resume:释放所有信号量。
public class SuspendResumeLock {
// 信号量最大许可
private static final int MAX_PERMITS = 10000;
// 信号量
private final Semaphore acquisitionSemaphore;
public SuspendResumeLock() {
this(true);
}
private SuspendResumeLock(final boolean createSemaphore) {
acquisitionSemaphore = (createSemaphore ? new Semaphore(MAX_PERMITS, true) : null);
}
public void acquire() throws SQLException {
// 先尝试获取信号量
if (acquisitionSemaphore.tryAcquire()) {
return;
}
// 尝试获取信号量失败,com.zaxxer.hikari.throwIfSuspended如果为true,直接抛出异常
else if (Boolean.getBoolean("com.zaxxer.hikari.throwIfSuspended")) {
throw new SQLTransientException("The pool is currently suspended and configured to throw exceptions upon acquisition");
}
// 尝试获取信号量失败,这里阻塞等待获取到新的许可
acquisitionSemaphore.acquireUninterruptibly();
}
public void release() {
acquisitionSemaphore.release();
}
public void suspend() {
acquisitionSemaphore.acquireUninterruptibly(MAX_PERMITS);
}
public void resume() {
acquisitionSemaphore.release(MAX_PERMITS);
}
}
FAUX_LOCK
是SuspendResumeLock
的一个空实现,作者希望通过JIT优化掉FAUX_LOCK相关的调用。
public class SuspendResumeLock {
public static final SuspendResumeLock FAUX_LOCK = new SuspendResumeLock(false) {
@Override
public void acquire() {}
@Override
public void release() {}
@Override
public void suspend() {}
@Override
public void resume() {}
};
}
initializeHouseKeepingExecutorService
initializeHouseKeepingExecutorService
创建housekeeper线程池,housekeeper线程池负责执行HouseKeeper
定时任务和连接到达maxLifeTime定时关闭。
private ScheduledExecutorService initializeHouseKeepingExecutorService() {
if (config.getScheduledExecutor() == null) {
// 线程工厂
final ThreadFactory threadFactory = Optional.ofNullable(config.getThreadFactory()).orElseGet(() -> new DefaultThreadFactory(poolName + " housekeeper", true));
// 核心线程数1个,最大线程数Integer.MAX_VALUE
// 拒绝策略:丢弃任务
final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy());
// 线程池执行shutdown后,是否还执行定时任务
// 默认true,设置为false
executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
// 取消任务后,是否将任务立即移出工作队列
// 默认false,设置为true
executor.setRemoveOnCancelPolicy(true);
return executor;
} else {
// 优先使用配置里的线程池
return config.getScheduledExecutor();
}
}
checkFailFast
checkFailFast
检查与数据库连通性,如果有问题直接快速失败。
private void checkFailFast() {
final long initializationTimeout = config.getInitializationFailTimeout();
// 默认initializationTimeout=1,一定会执行后面的操作
if (initializationTimeout < 0) {
return;
}
final long startTime = currentTime();
do {
// 这里面创建了数据库连接
// 梳理HirakiDatasource.getConnection流程时学习
final PoolEntry poolEntry = createPoolEntry();
if (poolEntry != null) {
// 如果最小连接数大于0
// 直接放到connectionBag里,可以直接被使用
if (config.getMinimumIdle() > 0) {
connectionBag.add(poolEntry);
}
else {
// 否则关闭连接
// 关闭连接相关内容后续学习
quietlyCloseConnection(poolEntry.close(), "(initialization check complete and minimumIdle is zero)");
}
return;
}
// 如果是ConnectionSetupException抛出异常
if (getLastConnectionFailure() instanceof ConnectionSetupException) {
throwPoolInitializationException(getLastConnectionFailure().getCause());
}
quietlySleep(SECONDS.toMillis(1));
} while (elapsedMillis(startTime) < initializationTimeout);
// 超时未创建成功数据库连接,抛出异常
if (initializationTimeout > 0) {
throwPoolInitializationException(getLastConnectionFailure());
}
}
addConnectionExecutor
this.addConnectionExecutor
线程池专门用于创建连接(PoolEntry)放入ConcurrentBag
。这个线程池的配置参数是:核心线程数1;最大线程数1;允许核心线程被回收,过期时间5秒;等待队列是外部传入的LinkedBlockingQueue,容量为maxPoolSize;拒绝策略是丢弃最老的任务。
closeConnectionExecutor与addConnectionExecutor配置一致,区别是拒绝策略为new ThreadPoolExecutor.CallerRunsPolicy()
由当前线程执行。
public HikariPool(final HikariConfig config) {
super(config);
// ...
final int maxPoolSize = config.getMaximumPoolSize();
// 等待队列 长度为maxPoolSize
LinkedBlockingQueue<Runnable> addConnectionQueue = new LinkedBlockingQueue<>(maxPoolSize);
// 等待队列视图
this.addConnectionQueueReadOnlyView = unmodifiableCollection(addConnectionQueue);
// addConnectionExecutor
this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardOldestPolicy());
// ...
}
public static ThreadPoolExecutor createThreadPoolExecutor(final BlockingQueue<Runnable> queue, final String threadName, ThreadFactory threadFactory, final RejectedExecutionHandler policy) {
if (threadFactory == null) {
threadFactory = new DefaultThreadFactory(threadName, true);
}
ThreadPoolExecutor executor = new ThreadPoolExecutor(1 , 1 , 5, SECONDS, queue, threadFactory, policy);
// 允许核心线程被回收
executor.allowCoreThreadTimeOut(true);
return executor;
}
启动HouseKeeper
The house keeping task to retire and maintain minimum idle connections.
HouseKeeper默认30s执行一次,负责执行下列任务
- 检测时钟倒退,尝试关闭所有连接。
- 过期连接关闭。超过最小连接数,且超过空闲连接时长的连接。
- 根据配置和当前情况,判断是否要添加连接,如果需要则提交任务到
addConnectionExecutor
。
HouseKeeper定时任务的逻辑后续再聊。
private final long housekeepingPeriodMs = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", SECONDS.toMillis(30));
public HikariPool(final HikariConfig config) {
// ...
this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);
// ...
}
总结
HikariCP启动在HikariDatasource
构造时触发,所以这一章介绍了四个构造方法。
其中最重要的是HikariPool
的构造方法。
- 创建了
ConcurrentBag
并将HikariPool自己作为IBagStateListener
。 - failfast的初始化校验。
- 创建适用于执行各类任务的线程池,例如
houseKeepingExecutorService
执行HouseKeeper
、addConnectionExecutor
执行创建连接等。
原创不易,欢迎评论、点赞和关注。欢迎关注公众号:程序猿阿越。