HikariCP 创建连接

article/2025/9/14 13:04:36

PoolEntry

PoolEntry 是 HikariCP 中对数据库物理连接的封装。 那我们现在探索问题的关键点就是:

  • PoolEntry.connection 是如何创建的
  • 连接是何时创建的;

我们先看下 HikariCP 中数据源、连接、连接池之间的关系。
HikariCP 中数据源、连接、连接池之间的关系

创建连接

连接池的初始化过程中 HikariCP 做了很多工作,如校验配置等。在此,我们只讨论连接的创建过程。在连接池的初始化过程中一共有 3 种创建连接的过程:

  1. 快速失败阶段;
  2. 管家线程 HouseKeeper 创建连接;
  3. 获取连接时连接数不够用;
    HikariCP 中创建连接的场景
    三种创建连接的场景都是通过 HikariPool#createPoolEntry 方法进行的。下面我们看一下 createPoolEntry 方法的具体内容:
   /*** Creating new poolEntry.  If maxLifetime is configured, create a future End-of-life task with 2.5% variance from* the maxLifetime time to ensure there is no massive die-off of Connections in the pool.*/private PoolEntry createPoolEntry(){try {final PoolEntry poolEntry = newPoolEntry();final long maxLifetime = config.getMaxLifetime();if (maxLifetime > 0) {// variance up to 2.5% of the maxlifetime// 对每个连接的 maxLifetime 设置一些偏差,避免大量连接同时失效final long variance = maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;final long lifetime = maxLifetime - variance;poolEntry.setFutureEol(houseKeepingExecutorService.schedule(new MaxLifetimeTask(poolEntry), lifetime, MILLISECONDS));}final long keepaliveTime = config.getKeepaliveTime();if (keepaliveTime > 0) {// variance up to 10% of the heartbeat timefinal long variance = ThreadLocalRandom.current().nextLong(keepaliveTime / 10);final long heartbeatTime = keepaliveTime - variance;poolEntry.setKeepalive(houseKeepingExecutorService.scheduleWithFixedDelay(new KeepaliveTask(poolEntry), heartbeatTime, heartbeatTime, MILLISECONDS));}return poolEntry;}catch (ConnectionSetupException e) {if (poolState == POOL_NORMAL) { // we check POOL_NORMAL to avoid a flood of messages if shutdown() is running concurrentlylogger.error("{} - Error thrown while acquiring connection from data source", poolName, e.getCause());lastConnectionFailure.set(e);}}catch (Exception e) {if (poolState == POOL_NORMAL) { // we check POOL_NORMAL to avoid a flood of messages if shutdown() is running concurrentlylogger.debug("{} - Cannot acquire connection from data source", poolName, e);}}return null;}

我们可以看到,创建连接 PoolEntry 时,会注册两个异步延时任务:

  1. MaxLifetimeTask:检查连接是否达到了最大存活时间。若达到了,则将连接 PoolEntry 设置为已驱逐状态:evit = true ,如果连接不是使用中状态的话则关闭连接,调用 addBagItem(final int waiting) 方法;
  2. KeepaliveTask:检查连接是否有效,如果连接失效,则将连接 PoolEntry 设置为已驱逐状态:evit = true

同时在注册这两个异步延时任务时,注意到对两个异步任务的延迟时间都做了特殊处理,分别增加了一定范围的时间变化(MaxLifetimeTask, 2.5%;KeepaliveTask:10%)。主要是避免同时连接过期,连接同时进行心跳检测。

创建物理连接

数据库物理连接的创建都是由 PoolBase#newConnection 完成的。代码具体内容如下:

   /*** Obtain connection from data source.** @return a Connection connection*/private Connection newConnection() throws Exception{final long start = currentTime();Connection connection = null;try {String username = config.getUsername();String password = config.getPassword();connection = (username == null) ? dataSource.getConnection() : dataSource.getConnection(username, password);if (connection == null) {throw new SQLTransientConnectionException("DataSource returned null unexpectedly");}setupConnection(connection);lastConnectionFailure.set(null);return connection;}catch (Exception e) {if (connection != null) {quietlyCloseConnection(connection, "(Failed to create/setup connection)");}else if (getLastConnectionFailure() == null) {logger.debug("{} - Failed to create/setup connection: {}", poolName, e.getMessage());}lastConnectionFailure.set(e);throw e;}finally {// tracker will be null during failFast checkif (metricsTracker != null) {metricsTracker.recordConnectionCreated(elapsedMillis(start));}}}

当 HikariConfig 没有配置 dataSource 时,DataSource#getConnection 是由 hikari 中的实现类 DriverDataSource#getConnection 完成的,其代码如下:

   @Overridepublic Connection getConnection(final String username, final String password) throws SQLException{final Properties cloned = (Properties) driverProperties.clone();if (username != null) {cloned.put(USER, username);if (cloned.containsKey("username")) {cloned.put("username", username);}}if (password != null) {cloned.put(PASSWORD, password);}return driver.connect(jdbcUrl, cloned);}

创建的连接 PoolEntry 通过 ConcurrentBag#add 加入到了连接池中:

   /*** Add a new object to the bag for others to borrow.** @param bagEntry an object to add to the bag*/public void add(final T bagEntry){if (closed) {LOGGER.info("ConcurrentBag has been closed, ignoring add()");throw new IllegalStateException("ConcurrentBag has been closed, ignoring add()");}sharedList.add(bagEntry);// spin until a thread takes it or none are waitingwhile (waiters.get() > 0 && bagEntry.getState() == STATE_NOT_IN_USE && !handoffQueue.offer(bagEntry)) {Thread.yield();}}

综上,HikariCP 创建连接的时序图如下:

HikariCP 创建连接的时序图

checkFailFast 阶段创建连接

连接的创建开始于连接池的初始化。无论我们以 HikariConfig 还是 no-args 的方式配置,连接池的初始化都是一样的,这个阶段会在快速失败阶段和启动管家线程的方式进行连接的创建:

   /*** Construct a HikariPool with the specified configuration.** @param config a HikariConfig instance*/public HikariPool(final HikariConfig config){super(config);this.connectionBag = new ConcurrentBag<>(this);this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;// 初始化管家定时执行服务this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();// 快速失败,这个阶段只创建了一个数据库连接checkFailFast();if (config.getMetricsTrackerFactory() != null) {setMetricsTrackerFactory(config.getMetricsTrackerFactory());}else {setMetricRegistry(config.getMetricRegistry());}// 设置健康检查setHealthCheckRegistry(config.getHealthCheckRegistry());// 注册 MBeanhandleMBeans(this, true);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());this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);// 开始执行管家任务,开始管理数据库连接// 在初始化阶段主要是添加数据库连接// 在后期也负责关闭空闲连接this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);if (Boolean.getBoolean("com.zaxxer.hikari.blockUntilFilled") && config.getInitializationFailTimeout() > 1) {addConnectionExecutor.setMaximumPoolSize(Math.min(16, Runtime.getRuntime().availableProcessors()));addConnectionExecutor.setCorePoolSize(Math.min(16, Runtime.getRuntime().availableProcessors()));final long startTime = currentTime();while (elapsedMillis(startTime) < config.getInitializationFailTimeout() && getTotalConnections() < config.getMinimumIdle()) {quietlySleep(MILLISECONDS.toMillis(100));}addConnectionExecutor.setCorePoolSize(1);addConnectionExecutor.setMaximumPoolSize(1);}}

checkFailFast 具体内容如下:

   /*** If initializationFailFast is configured, check that we have DB connectivity.** @throws PoolInitializationException if fails to create or validate connection* @see HikariConfig#setInitializationFailTimeout(long)*/private void checkFailFast(){final long initializationTimeout = config.getInitializationFailTimeout();if (initializationTimeout < 0) {return;}final long startTime = currentTime();do {final PoolEntry poolEntry = createPoolEntry();if (poolEntry != null) {if (config.getMinimumIdle() > 0) {connectionBag.add(poolEntry);logger.debug("{} - Added connection {}", poolName, poolEntry.connection);}else {quietlyCloseConnection(poolEntry.close(), "(initialization check complete and minimumIdle is zero)");}return;}if (getLastConnectionFailure() instanceof ConnectionSetupException) {throwPoolInitializationException(getLastConnectionFailure().getCause());}quietlySleep(SECONDS.toMillis(1));} while (elapsedMillis(startTime) < initializationTimeout);if (initializationTimeout > 0) {throwPoolInitializationException(getLastConnectionFailure());}}

从上可以看出,快速失败检查阶段创建了数据库物理连接实在创建连接池,此时如果创建数据库物理连接失败,则创建连接池的过程就会停止,不会进入真正的创建连接池的阶段。

HouseKeeper 创建连接

另外,在连接池的构造过程中,我们可以看到创建了多个线程池:
addConnectionExecutor:用于物理连接的创建。
closeConnectionExecutor:用于物理连接的关闭。

      this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardOldestPolicy());

addConnectionExecutor 采用的是 DiscardPolicy ,在任务满了的情况下丢弃被拒绝的任务,不会产生异常。任务队列的长度就是 maxPoolSize .

连接池的构造最后阶段即是开启了管家线程 HouseKeeper . 管家线程的主要功能就是管理线程池,创建、关闭连接。

        this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);
   private final class HouseKeeper implements Runnable{private volatile long previous = plusMillis(currentTime(), -housekeepingPeriodMs);@Overridepublic void run(){try {// refresh values in case they changed via MBeanconnectionTimeout = config.getConnectionTimeout();validationTimeout = config.getValidationTimeout();leakTaskFactory.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());catalog = (config.getCatalog() != null && !config.getCatalog().equals(catalog)) ? config.getCatalog() : catalog;final long idleTimeout = config.getIdleTimeout();final long now = currentTime();// Detect retrograde time, allowing +128ms as per NTP spec.if (plusMillis(now, 128) < plusMillis(previous, housekeepingPeriodMs)) {logger.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.",poolName, elapsedDisplayString(previous, now));previous = now;softEvictConnections();return;}else if (now > plusMillis(previous, (3 * housekeepingPeriodMs) / 2)) {// No point evicting for forward clock motion, this merely accelerates connection retirement anywaylogger.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", poolName, elapsedDisplayString(previous, now));}previous = now;// 关闭连接,维持最小连接数String afterPrefix = "Pool ";if (idleTimeout > 0L && config.getMinimumIdle() < config.getMaximumPoolSize()) {logPoolState("Before cleanup ");afterPrefix = "After cleanup  ";final List<PoolEntry> notInUse = connectionBag.values(STATE_NOT_IN_USE);int toRemove = notInUse.size() - config.getMinimumIdle();for (PoolEntry entry : notInUse) {if (toRemove > 0 && elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {closeConnection(entry, "(connection has passed idleTimeout)");toRemove--;}}}//logPoolState(afterPrefix);// 增加连接,维持最小连接数fillPool(); // Try to maintain minimum connections}catch (Exception e) {logger.error("Unexpected exception in housekeeping task", e);}}}

其中 fillPool() 会通过 addConnectionExecutor 调用 PoolEntryCreator 进行连接的创建。代码如下:

   /*** Fill pool up from current idle connections (as they are perceived at the point of execution) to minimumIdle connections.*/private synchronized void fillPool(){final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())- addConnectionQueueReadOnlyView.size();if (connectionsToAdd <= 0) logger.debug("{} - Fill pool skipped, pool is at sufficient level.", poolName);for (int i = 0; i < connectionsToAdd; i++) {addConnectionExecutor.submit((i < connectionsToAdd - 1) ? poolEntryCreator : postFillPoolEntryCreator);}}

从 fillPool() 代码可看出其主要职责就是维持连接池最小的连接数。

总结

  1. HikariCP 中数据源、连接、连接池之间的关系;
  2. HikariCP 创建连接的过程;
  3. HikariCP 会在初始化连接池的快速阶段,管家线程运行中,获取连接但连接不够用时创建连接;

http://chatgpt.dhexx.cn/article/IXRCmQw6.shtml

相关文章

hikaricp mysql_配置HikariCP连接池

配置HikariCP连接池 数据库配置 spring.datasource.driver-class-namecom.mysql.jdbc.Driver 指定 spring 连接数据源驱动 spring.datasource.typecom.zaxxer.hikari.HikariDataSource 指定 spring 连接数据源类型 spring.datasource.urljdbc:mysql:///dbgoods?serverTimezone…

HikariCP连接池

背景 自己在写点代码的时候之前pom文件的内容都是从前一个项目中复制的&#xff0c;并没有认真分析一下各种依赖的关系&#xff0c;有时候重复的问题会出现好多次&#xff0c;比如说连接数据库报错&#xff0c;有时候配置没有问题&#xff0c;可能是依赖的jar包的问题&#xf…

HikariCP数据库连接池详解

HikariCP数据库连接池详解 1. 数据库连接池概述2. 为什么需要连接池3. HikariCP概述4. HikariCP特点5. HikariCP配置6. HikariCP案例验证 1. 数据库连接池概述 连接池是一种常用的技术&#xff0c;为什么需要连接池呢&#xff1f; 这个需要从TCP说起。假如我们的服务器跟数据库…

hikaricp mysql_HikariCP数据库连接池

摘要: 原创出处 hacpai.com/article/1582096971127 「jianzh5」欢迎转载&#xff0c;保留摘要&#xff0c;谢谢&#xff01; 什么是数据库连接池&#xff1a; 连接池是一种常用的技术&#xff0c;为什么需要连接池呢&#xff1f;这个需要从TCP说起。假如我们的服务器跟数据库没…

hikaricp使用

hikaricp数据库连接池是目前很流行的数据源 1.需要引入mysql的驱动、hikaricp依赖 <properties><java.version>1.8</java.version><hikari.version>2.7.9</hikari.version><mysql.version>8.0.13</mysql.version></properties&g…

自定义HikariCP连接池

文章目录 一、简介1、概述2、地址 二、配置参数1、Hikari原生参数2、Springboot中参数 三、springboot中使用四、自定义数据源1、各模块2、完整代码3、多数据源 五、多数据源dynamic中使用1、简介2、引入依赖3、参数配置 六、XMind整理 一、简介 1、概述 官方解释&#xff1a…

HikariCP源码分析

文章目录 1. 基本用法1.1 添加依赖1.2 创建DataSource1.3 获取连接 2. 源码分析2.1 API2.2 Pool2.2.1 获取连接2.2.2 添加连接2.2.3 维护连接 2.3 metrics2.3.1 dropwizard2.3.2 prometheus 3. 最佳实践 HikariCP是一个快速&#xff0c;简单可靠的JDBC连接池&#xff0c;Spring…

hikaricp mysql_HikariCP

软件简介 HikariCP 是一个高性能的 JDBC 连接池组件。下图是性能的比较测试结果&#xff1a; 使用方法&#xff1a; HikariConfig config new HikariConfig(); config.setMaximumPoolSize(100); config.setDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlData…

数据库连接池HikariCP

HikariCP 现在已经有很多公司在使用HikariCP了&#xff0c;HikariCP还成为了SpringBoot默认的连接池&#xff0c;伴随着SpringBoot和微服务&#xff0c;HikariCP 必将迎来广泛的普及。 下面带大家从源码角度分析一下HikariCP为什么能够被Spring Boot 青睐&#xff0c;文章目录…

HikariCP 了解一下

作者 | 黄永灿 后端开发攻城狮&#xff0c;关注服务端技术与性能优化。 前言 在我们的工作中&#xff0c;免不了要和数据库打交道&#xff0c;而要想和数据库打好交道&#xff0c;选择一款合适的数据库连接池就至关重要&#xff0c;目前业界广为人知的数据库连接池有 Tomcat JD…

离线数仓-03-数仓系统搭建(ODS,DIM,DWD,DWS,ADS)

文章目录 数仓分层为什么要分层数据运营层&#xff1a;ODS&#xff08;Operational Data Store&#xff09;数据仓库层&#xff1a;DW&#xff08;Data Warehouse&#xff09;维表层&#xff1a;DIM&#xff08;Dimension&#xff09;数据明细层&#xff1a;DWD&#xff08;Dat…

数仓数据分层(ODS DWD DWS ADS)换个角度看

数仓数据分层简介 1. 背景 数仓是什么, 其实就是存储数据,体现历史变化的一个数据仓库. 因为互联网时代到来,基于数据量的大小,分为了传统数仓和现代数仓.传统数仓,使用传统的关系型数据库进行数据存储,因为关系型数据库本身可以使用SQL以及函数等做数据分析.所以把数据存储和…

湖仓一体电商项目(十):业务实现之编写写入DWD层业务代码

文章目录 业务实现之编写写入DWD层业务代码 一、代码编写

【实时数仓】CDC简介、实现DWD层业务数据的处理(主要任务、接收kafka数据、动态分流*****)

文章目录 一 CDC简介1 什么是CDC2 CDC的种类3 Flink-CDC 二 准备业务数据-DWD层1 主要任务&#xff08;1&#xff09;接收Kafka数据&#xff0c;过滤空值数据&#xff08;2&#xff09;实现动态分流功能&#xff08;3&#xff09;把分好的流保存到对应表、主题中 2 接收Kafka数…

数仓开发之DWD层(二)

目录 三&#xff1a;流量域用户跳出事务事实表 3.1 主要任务 3.2 思路分析 3.3 图解 3.4 代码 四&#xff1a;交易域加购事务事实表 4.1 主要任务 4.2 思路分析 4.3 图解 4.4 代码 三&#xff1a;流量域用户跳出事务事实表 3.1 主要任务 过滤用户跳出明细数据。 3.2 思…

电商数仓(dwd 层)

一、dwd 层介绍 1、对用户行为数据解析。 2、对核心数据进行判空过滤。 3、对业务数据采用维度模型重新建模&#xff0c;即维度退化。 二、dwd 层用户行为数据 2.1 用户行为启动表 dwd_start_log 1、数据来源 ods_start_log -> dwd_start_log 2、表的创建 drop table…

详解数仓中的数据分层:ODS、DWD、DWM、DWS、ADS

何为数仓DW Data warehouse(可简写为DW或者DWH)数据仓库,是在数据库已经大量存在的情况下,它是一整套包括了etl、调度、建模在内的完整的理论体系。 数据仓库的方案建设的目的,是为前端查询和分析作为基础,主要应用于OLAP(on-line Analytical Processing),支持复杂的分析…

万字详解数仓分层设计架构 ODS-DWD-DWS-ADS

一、数仓建模的意义&#xff0c;为什么要对数据仓库分层&#xff1f; 只有数据模型将数据有序的组织和存储起来之后&#xff0c;大数据才能得到高性能、低成本、高效率、高质量的使用。 1、分层意义 1&#xff09;清晰数据结构&#xff1a;每一个数据分层都有它的作用域&#x…

数仓开发之DWD层(四)

目录 十一&#xff1a;工具域优惠券领取事务事实表 11.1 主要任务&#xff1a; 11.2 思路分析&#xff1a; 11.3 图解&#xff1a; 十二&#xff1a;工具域优惠券使用&#xff08;下单&#xff09;事务事实表 12.1 主要任务&#xff1a; 12.2 思路分析&#xff1a; 12.3…