亲宝软件园·资讯

展开

SpringBoot HikariCP配置项及源码解析

MrDong先生 人气:0

前言

在SpringBoot2.0之后,采用的默认数据库连接池就是Hikari,是一款非常强大,高效,并且号称“史上最快连接池”。我们知道的连接池有C3P0,DBCP,Druid它们都比较成熟稳定,但性能不是十分好。

我们在日常的编码中,通常会将一些对象保存起来,这主要考虑的是对象的创建成本;比如像线程资源、数据库连接资源或者 TCP 连接等,这类对象的初始化通常要花费比较长的时间,如果频繁地申请和销毁,就会耗费大量的系统资源,造成不必要的性能损失,于是在Java 中,池化技术应用非常广泛。在软件行开发中,软件的性能是占主导地位的,于是HikariCP就在众多数据库连接池中脱颖而出。

为什么HikariCP性能高

下面为大家附上一张官方的性能测试图,我们可以从图上很直观的看出HikariCP的性能卓越:

常用配置项

autoCommit

控制从池返回的连接的默认自动提交行为,默认为true

connectionTimeout

控制客户端等待来自池的连接的最大毫秒数。

如果在没有连接可用的情况下超过此时间,则将抛出 SQLException。可接受的最低连接超时时间为 250 毫秒。默认值:30000(30 秒)

idleTimeout

连接允许在池中闲置的最长时间

如果idleTimeout+1秒>maxLifetime 且 maxLifetime>0,则会被重置为0(代表永远不会退出);如果idleTimeout!=0且小于10秒,则会被重置为10秒

这是HikariCP用来判断是否应该从连接池移除空闲连接的一个重要的配置。负责剔除的也还是HouseKeeper这个定时任务,值为0时,HouseKeeper不会移除空闲连接,直到到达maxLifetime后,才会移除,默认值也就是0。
正常情况下,HouseKeeper会找到所有状态为空闲的连接队列,遍历一遍,将空闲超时到达idleTimeout且未超过minimumIdle数量的连接的批量移除。

maxLifetime

池中连接最长生命周期;如果不等于0且小于30秒则会被重置回30分钟

了解这个值的作用前,先了解一下MySQLwait_timeout的作用:MySQL 为了防止空闲连接浪费,占用资源,在超过wait_timeout时间后,会主动关闭该连接,清理资源;默认是28800s,也就是8小时。简而言之就是MySQL会在某个连接超过8小时还没有任何请求时自动断开连接,但是HikariCP如何知道池子里的连接有没有超过这个时间呢?所以就有了maxLifetime,配置后HikariCP会把空闲链接超过这个时间的给剔除掉,防止获取到已经关闭的连接导致异常。

connectionTestQuery

将在从池中向您提供连接之前执行的查询,以验证与数据库的连接是否仍然有效,如select 1

minimumIdle

池中维护的最小空闲连接数;minIdle<0或者minIdle>maxPoolSize,则被重置为maxPoolSize

HikariCP Pool创建时,会启动一个HouseKeeper定时任务,每隔30s,判断空闲线程数低于minimumIdle,并且当前线程池总连接数小于maximumPoolSize,就建立和MySQL的一个长连接,然后加入到连接池中。官方建议minimumIdlemaximumPoolSize保持一致。 因为HikariCPHouseKeeper在发现idleTimeout>0 并且 minimumIdle < maximumPoolSize时,先会去扫描一遍需要移除空闲连接,和MySQL断开连接。然后再一次性补满空闲连接数至到minimumIdle

maximumPoolSize

池中最大连接数,其实就是线程池中队列的大小,默认大小为10(包括闲置和使用中的连接)

如果maxPoolSize小于1,则会被重置。当minIdle<=0被重置为DEFAULT_POOL_SIZE则为10;如果minIdle>0则重置为minIdle的值

HikariCP架构

分析源码之前,先给大家介绍一下HikariCP的整体架构,整体架构和DBCP2 的有点类似(由此可见 HikariCP 与 DBCP2 性能差异并不是由于架构设计),下面我总结了几点,来和大家一起探讨下:

源码解析

HikariConfig

HikariConfig保存了所有连接池配置,另外实现了HikariConfigMXBean接口,有些配置可以利用JMX运行时变更。核心配置项属性会在下面给大家介绍,这边Dong哥就简单介绍一下了。

HikariPool

getConnection

  public Connection getConnection(final long hardTimeout) throws SQLException
   {
      //这里是防止线程池处于暂停状态(通常不允许线程池可暂停)
      suspendResumeLock.acquire();
      final long startTime = currentTime();
      try {
         long timeout = hardTimeout;
         do {
            //PoolEntry 用于跟踪connection实例,里面包装了Connection;
            //从connectionBag中获取一个对象,并且检测是否可用
            PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
            if (poolEntry == null) {
               break; // We timed out... break and throw exception
            }
            final long now = currentTime();
            //1、已被标记为驱逐 2、已超过最大存活时间 3、链接已死
            if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(poolEntry.connection))) {
               closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);
               //刷新超时时间
               timeout = hardTimeout - elapsedMillis(startTime);
            }
            else {
               metricsTracker.recordBorrowStats(poolEntry, startTime);
               return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
            }
            //如果没超时则再次获取
         } while (timeout > 0L);
		 //超时时间到仍未获取到链接则抛出 TimeoutException
         metricsTracker.recordBorrowTimeoutStats(startTime);
         throw createTimeoutException(startTime);
      }
      catch (InterruptedException e) {
         Thread.currentThread().interrupt();
         throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
      }
      finally {
         suspendResumeLock.release();
      }
   }

校验

/**
* startTime 上次使用时间
* endTime   当前时间
*/
static long elapsedMillis(long startTime, long endTime) {
    return CLOCK.elapsedMillis0(startTime, endTime);
}
 boolean isConnectionAlive(final Connection connection)
   {
      try {
         try {
            //如果支持Connection networkTimeout,则优先使用并设置
            setNetworkTimeout(connection, validationTimeout);
            final int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;
	    //如果jdbc实现支持jdbc4 则使用jdbc4 Connection的isValid方法检测
            if (isUseJdbc4Validation) {
               return connection.isValid(validationSeconds);
            }
	    //查询数据库检测连接可用性
            try (Statement statement = connection.createStatement()) {
              //如果不支持Connection networkTimeout 则设置Statement queryTimeout
               if (isNetworkTimeoutSupported != TRUE) {
                  setQueryTimeout(statement, validationSeconds);
               }
               statement.execute(config.getConnectionTestQuery());
            }
         }
         finally {
            setNetworkTimeout(connection, networkTimeout);
            if (isIsolateInternalQueries &amp;&amp; !isAutoCommit) {
               connection.rollback();
            }
         }
         return true;
      }
      catch (Exception e) {
         lastConnectionFailure.set(e);
         LOGGER.warn("{} - Failed to validate connection {} ({}). Possibly consider using a shorter maxLifetime value.",
                     poolName, connection, e.getMessage());
         //捕获到异常,说明链接不可用。(connection is unavailable)
         return false;
      }
   }

HouseKeeper

HouseKeeper负责保持,我们始终有minimumIdle空闲链接可用

 private final class HouseKeeper implements Runnable
   {
      //默认30s,执行一次
      private volatile long previous = plusMillis(currentTime(), -HOUSEKEEPING_PERIOD_MS);
      @Override
      public void run()
      {
         try {
            //省略......
            String afterPrefix = "Pool ";
            if (idleTimeout &gt; 0L &amp;&amp; config.getMinimumIdle() &lt; config.getMaximumPoolSize()) {
               logPoolState("Before cleanup ");
               afterPrefix = "After cleanup  ";
	       //空闲链接数
               final List&lt;PoolEntry&gt; notInUse = connectionBag.values(STATE_NOT_IN_USE);
               int toRemove = notInUse.size() - config.getMinimumIdle();
               for (PoolEntry entry : notInUse) {
                  if (toRemove &gt; 0 &amp;&amp; elapsedMillis(entry.lastAccessed, now) &gt; idleTimeout &amp;&amp; connectionBag.reserve(entry)) {
                     //关闭过多的空闲超时链接
                     closeConnection(entry, "(connection has passed idleTimeout)");
                     toRemove--;
                  }
               }
            }
	    //记录pool状态信息
            logPoolState(afterPrefix);
	    //补充空闲链接
            fillPool(); 
         }
         catch (Exception e) {
            LOGGER.error("Unexpected exception in housekeeping task", e);
         }
      }
   }

HouseKeeper其实是一个线程,也是写在HikariPool类里面的一个内部类,主要负责保持 minimumIdle 的空闲链接。HouseKeeper也用到了validationTimeout, 并且会根据minimumIdle配置,通过fill 或者 remove保持最少空闲链接数。
HouseKeeper线程初始化:

 public HikariPool(final HikariConfig config)
   {
      super(config);
      this.connectionBag = new ConcurrentBag<>(this);
      this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
      //执行初始化
      this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
      //省略......
      }
   }
private ScheduledExecutorService initializeHouseKeepingExecutorService() {
        if (this.config.getScheduledExecutor() == null) {
            ThreadFactory threadFactory = (ThreadFactory)Optional.ofNullable(this.config.getThreadFactory()).orElseGet(() -> {
                return new DefaultThreadFactory(this.poolName + " housekeeper", true);
            });
            //ScheduledThreadPoolExecutor是ThreadPoolExecutor类的子类,Java推荐仅在开发定时任务程序时采用ScheduledThreadPoolExecutor类
            ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, threadFactory, new DiscardPolicy());
            //传入false,则执行shutdown()方法之后,待处理的任务将不会被执行
            executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
            //取消任务后,判断是否需要从阻塞队列中移除任务
            executor.setRemoveOnCancelPolicy(true);
            return executor;
        } else {
            return this.config.getScheduledExecutor();
        }
    }

HikariDataSource

HikariDataSource 非常重要,主要用于操作HikariPool获取连接,并且能够清除空闲连接。

public class HikariDataSource extends HikariConfig implements DataSource, Closeable {
   private final AtomicBoolean isShutdown = new AtomicBoolean();
   //final修饰,构造时决定,如果使用无参构造为null,使用有参构造和pool一样
   private final HikariPool fastPathPool;
   //volatile修饰,无参构造不会设置pool,在getConnection时构造pool,有参构造和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();
   }
}

加载全部内容

相关教程
猜你喜欢
用户评论