HikariCP源码阅读(三)HikariCP启动

1,222 阅读4分钟

前言

紧接上一章,这一章看看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方法中创建了DriverDataSourceDriverDataSource是真正负责获取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_LOCKSuspendResumeLock的一个空实现,作者希望通过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执行HouseKeeperaddConnectionExecutor执行创建连接等。

原创不易,欢迎评论、点赞和关注。欢迎关注公众号:程序猿阿越。