自学内容网 自学内容网

如何设计一款高性能的数据库连接工具?过来看这里,这是一款号称拥有“光”一样链接速度的工具,我们看它是怎样实现的(第二节:Hikari是如何实现的底层逻辑)

续接上节如何设计一款高性能的数据库连接工具?过来看这里,这是一款号称拥有“光”一样链接速度的工具,我们看它是怎样实现的(第一节:数据库链接技术发展)-CSDN博客

接下来我们继续分析Hikari,它为什么怎么快呢?因为HikariCP没有spring.factories(在spring boot应用启动中会获取应用的ClassLoader进而获得所有jar包并且将读取每个jar包下的META-INF/spring.factories)接下来所以我们从代码结构进行分析

1.HikariDataSource类继承图

DataSource代表数据库连接,而HikariDataSource就是Hikari数据库连接,就是Hikari的核心,下面看一下它的类图

 HikariDataSource 继承了 HikariConfig,实现了 DataSource 接口和Closeable接口

  • HikariConfig,是Hikari的配置类,有着jdbcUrl、用户名密码、超时时间、数据库连接池连接数等成员变量属性
  • DataSource,是一个接口(javax.sql.DataSource),就是数据库连接池在Java类中的体现,对JDBC框架来说不需要关注Connection怎么来的,只需要调这个接口的方法获取即可

DataSource里面定义了两个方法:一个是无参数的获取连接、一个是通过用户名和密码获取数据库连接的


  Connection getConnection() throws SQLException;

  Connection getConnection(String username, String password)
    throws SQLException;

HikariConfig里面定义了Hikari连接池的参数,有着jdbcUrl、用户名密码、超时时间、数据库连接池连接数等成员变量属性,其中构造函数中默认初始化了一些参数值

 public HikariConfig()
   {
      dataSourceProperties = new Properties();
      healthCheckProperties = new Properties();

      minIdle = -1;
      maxPoolSize = -1;
      maxLifetime = MAX_LIFETIME;
      connectionTimeout = CONNECTION_TIMEOUT;
      validationTimeout = VALIDATION_TIMEOUT;
      idleTimeout = IDLE_TIMEOUT;
      initializationFailTimeout = 1;
      isAutoCommit = true;
      keepaliveTime = DEFAULT_KEEPALIVE_TIME;

      String systemProp = System.getProperty("hikaricp.configurationFile");
      if (systemProp != null) {
         loadProperties(systemProp);
      }
   }

2.HikariDataSource的成员结构

  • isShutdown,一个AtomicBoolean(cas乐观锁)用来标识该dataSource是否已经关闭
  • fastPathPool 和 pool,都是指向HikariPool结构的引用(Hikari优化用来提升性能的手段)
  • 若干继承自HikariConfig的数据库连接池配置属性
//实现DataSource接口里面的getConnection()方法
@Override
   public Connection getConnection() throws SQLException
   {
//判断数据库连接是否已经关闭
      if (isClosed()) {
         throw new SQLException("HikariDataSource " + this + " has been closed.");
      }
//首先,从fastPathPool的连接池里面去获取连接
      if (fastPathPool != null) {
         return fastPathPool.getConnection();
      }

      // See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
//如果没有获取到就去创建一个连接,创建的时候采用锁机制,避免线程不安全
      HikariPool result = pool;
      if (result == null) {
         synchronized (this) {
            result = pool;
            if (result == null) {
               validate();
               LOGGER.info("{} - Starting...", getPoolName());
               try {
                  pool = result = new HikariPool(this);
                  this.seal();
               }
               catch (PoolInitializationException pie) {
                  if (pie.getCause() instanceof SQLException) {
                     throw (SQLException) pie.getCause();
                  }
                  else {
                     throw pie;
                  }
               }
               LOGGER.info("{} - Start completed.", getPoolName());
            }
         }
      }

      return result.getConnection();
   }

 3.HikariPool连接池管理

它继承了HikariPoolMXBean,实现了PoolBase类

连接池初始化流程

连接池源码

public HikariPool(final HikariConfig config)
   {
//利用config初始化各种连接池属性,并且产生一个用于生产物理连接的数据源DriverDataSource
      super(config);
//初始化存放连接对象的核心类ConcurrentBag
      this.connectionBag = new ConcurrentBag<>(this);
//初始化型号量,用于控制获取连接的速度,isAllowPoolSuspension=true生效
      this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
//初始化一个延时任务线程池类型的对象houseKeepingExecutorService,用于后续执行一些延时/定时类任务,
   // 比如连接泄漏检查延时任务,除此之外maxLifeTime后主动回收关闭连接也是交由该对象来执行的
      this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
//预热连接池,HikariCP会在该流程的checkFailFast里初始化好一个连接对象放进池子内,
   // 当然触发该流程得保证initializationTimeout > 0(默认值1),
   // 这个配置属性表示留给预热操作的时间,默认值1在预热失败时不会发生重试
      checkFailFast();

      if (config.getMetricsTrackerFactory() != null) {
         setMetricsTrackerFactory(config.getMetricsTrackerFactory());
      }
      else {
//注入MetricRegistry来实现对连接池指标的收集。这样我们可以较为方便的监控连接池的运行状态
         setMetricRegistry(config.getMetricRegistry());
      }

      setHealthCheckRegistry(config.getHealthCheckRegistry());

      handleMBeans(this, true);

      ThreadFactory threadFactory = config.getThreadFactory();

      final int maxPoolSize = config.getMaximumPoolSize();
      LinkedBlockingQueue<Runnable> addConnectionQueue = new LinkedBlockingQueue<>(maxPoolSize);
      this.addConnectionQueueReadOnlyView = unmodifiableCollection(addConnectionQueue);
//初始化一个线程池对象addConnectionExecutor,用于后续扩充连接对象
      this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardOldestPolicy());
//初始化一个线程池对象closeConnectionExecutor,用于关闭一些连接对象
      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);
      }
   }
  • 利用config初始化各种连接池属性,并且产生一个用于生产物理连接的数据源DriverDataSource
  • 初始化存放连接对象的核心类ConcurrentBag
  • 初始化一个延时任务线程池类型的对象houseKeepingExecutorService,用于后续执行一些延时/定时类任务,比如连接泄漏检查延时任务,除此之外maxLifeTime后主动回收关闭连接也是交由该对象来执行的
  • 预热连接池,HikariCP会在该流程的checkFailFast里初始化好一个连接对象放进池子内,当然触发该流程得保证initializationTimeout > 0(默认值1),这个配置属性表示留给预热操作的时间,默认值1在预热失败时不会发生重试
  • 初始化一个线程池对象addConnectionExecutor,用于后续扩充连接对象

从上面的代码可以看到一个方法,checkFailFast(),快速失败。避免在初始化中因为多次尝试而造成性能损耗,而且HikariCP仅预热一个连接对象。

4.HouseKeeper管家

上面HikariPool的维护,主要是靠HouseKeeper来维护管理连接池中的链接,我们看错看代码发现HouseKeeper实现了Runnable接口,说明他是另起了一个分支线程来执行动作的

 @Override
      public void run()
      {
         try {
            // refresh values in case they changed via MBean
            connectionTimeout = 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 anyway
               logger.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);
         }
      }

HouseKeeper主要工作如下: 

  1. 检测系统时间的准确性,系统时间被用户篡改过,则处理清空连接池
  2. 对空闲的连接,根据idleTimeout属性清理过期连接
  3. 在初始化或清理过后,填充新的连接到连接池中

5.回顾一下

我们读代码,发现HikariCP实现原理,实现了Java定义的DataSource接口,可以实现DataSource链接的创建,所有的链接,通过HikariPool进行管理的,HikariPool提供了快速检测失败的方法,可以加快链接快速失败,最后就是提供了HouseKeeper来管理HikariPool连接池内容。

下一节我们将继续分享Hikari优化连接池存储容器ConcurrentBag


原文地址:https://blog.csdn.net/Scalzdp/article/details/142456424

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!