HikariPool數(shù)據(jù)庫連接池配置詳解

1. 簡介

Hikari連接池目前公認是性能最高的數(shù)據(jù)庫連接池旱爆,同時也是SpringBoot2.0以后默認使用的數(shù)據(jù)庫連接池仿吞。

2. 關鍵配置

image.png

這些參數(shù)在不指定時會有默認值,默認值經(jīng)過validate方法,會賦不同的值鬼佣,下面看下具體的源碼實現(xiàn)。
dataSource的初始化

    @Bean(name = "dataSource")
    @Primary
    public HikariDataSource dataSource() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName("className");
        hikariConfig.setUsername("user");
        hikariConfig.setPassword("password");
        hikariConfig.setJdbcUrl("jdbcurl");
        hikariConfig.setMaximumPoolSize(100);
        hikariConfig.setConnectionTimeout(500);
        return new HikariDataSource(hikariConfig);
    }

HikariConfig無參構造方法

  private static final long CONNECTION_TIMEOUT = SECONDS.toMillis(30);
   private static final long VALIDATION_TIMEOUT = SECONDS.toMillis(5);
   private static final long IDLE_TIMEOUT = MINUTES.toMillis(10);
   private static final long MAX_LIFETIME = MINUTES.toMillis(30);
   private static final int DEFAULT_POOL_SIZE = 10;
  /**
    * Default constructor
    */
   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;

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

HikariDataSource初始化方法

/**
    * Construct a HikariDataSource with the specified configuration.  The
    * {@link HikariConfig} is copied and the pool is started by invoking this
    * constructor.
    *
    * The {@link HikariConfig} can be modified without affecting the HikariDataSource
    * and used to initialize another HikariDataSource instance.
    *
    * @param configuration a HikariConfig instance
    */
   public HikariDataSource(HikariConfig configuration)
   {
      // 對參數(shù)中不合理的進行修改
      configuration.validate();
      configuration.copyStateTo(this);

      LOGGER.info("{} - Starting...", configuration.getPoolName());
      // 構造時就生產(chǎn)pool霜浴,不用等到使用才生成
      pool = fastPathPool = new HikariPool(this);
      LOGGER.info("{} - Start completed.", configuration.getPoolName());

      this.seal();
   }

validate方法中的validateNumerics

 private static final long CONNECTION_TIMEOUT = SECONDS.toMillis(30);
   private static final long VALIDATION_TIMEOUT = SECONDS.toMillis(5);
   private static final long IDLE_TIMEOUT = MINUTES.toMillis(10);
   private static final long MAX_LIFETIME = MINUTES.toMillis(30);
   private static final int DEFAULT_POOL_SIZE = 10;
private void validateNumerics()
   {
      if (maxLifetime != 0 && maxLifetime < SECONDS.toMillis(30)) {
         LOGGER.warn("{} - maxLifetime is less than 30000ms, setting to default {}ms.", poolName, MAX_LIFETIME);
         maxLifetime = MAX_LIFETIME;
      }

      if (idleTimeout + SECONDS.toMillis(1) > maxLifetime && maxLifetime > 0) {
         LOGGER.warn("{} - idleTimeout is close to or more than maxLifetime, disabling it.", poolName);
         idleTimeout = 0;
      }

      if (idleTimeout != 0 && idleTimeout < SECONDS.toMillis(10)) {
         LOGGER.warn("{} - idleTimeout is less than 10000ms, setting to default {}ms.", poolName, IDLE_TIMEOUT);
         idleTimeout = IDLE_TIMEOUT;
      }

      if (leakDetectionThreshold > 0 && !unitTest) {
         if (leakDetectionThreshold < SECONDS.toMillis(2) || (leakDetectionThreshold > maxLifetime && maxLifetime > 0)) {
            LOGGER.warn("{} - leakDetectionThreshold is less than 2000ms or more than maxLifetime, disabling it.", poolName);
            leakDetectionThreshold = 0;
         }
      }

      if (connectionTimeout < 250) {
         LOGGER.warn("{} - connectionTimeout is less than 250ms, setting to {}ms.", poolName, CONNECTION_TIMEOUT);
         connectionTimeout = CONNECTION_TIMEOUT;
      }

      if (validationTimeout < 250) {
         LOGGER.warn("{} - validationTimeout is less than 250ms, setting to {}ms.", poolName, VALIDATION_TIMEOUT);
         validationTimeout = VALIDATION_TIMEOUT;
      }

      if (maxPoolSize < 1) {
         maxPoolSize = (minIdle <= 0) ? DEFAULT_POOL_SIZE : minIdle;
      }

      if (minIdle < 0 || minIdle > maxPoolSize) {
         minIdle = maxPoolSize;
      }
   }

3 Hikari源碼解析

3.1 獲取連接

1沮趣、Hikari中的核心類為HikariDataSource,表示Hikari連接池中的數(shù)據(jù)源,實現(xiàn)了DataSource接口的getConnection方法坷随,getConnection方法源碼如下:

   /** 連接池對象
     * fastPathPool 會在初始化時創(chuàng)建
     * pool 是在獲取連接數(shù)創(chuàng)建
     * volatile修飾pool導致每次讀pool都要從主存加載房铭,每次寫也要寫回主存驻龟,性能不如沒volatile修飾的fastPathPool
     * */
    private final HikariPool fastPathPool;
    private volatile HikariPool pool;

    /** 獲取連接*/
    public Connection getConnection() throws SQLException
    {
        if (isClosed()) {
            throw new SQLException("HikariDataSource " + this + " has been closed.");
        }
        /** 如果fastPathPool存在則直接獲取連接,有參構造方法都有*/
        if (fastPathPool != null) {
            return fastPathPool.getConnection();
        }
        /** 如果沒有fastPathPool 則創(chuàng)建HikariPool對象 */
       // 雙檢實現(xiàn)單例缸匪,pool用volatile修飾翁狐,防止指令重排序
        HikariPool result = pool;
        if (result == null) {
            synchronized (this) {
                result = pool;
                if (result == null) {
                    validate();
                    LOGGER.info("{} - Starting...", getPoolName());
                    try {
                        /** 初始化創(chuàng)建HikariPool對象*/
                        pool = result = new HikariPool(this);
                        this.seal();
                    }
                    catch (PoolInitializationException pie) {
                        //
                    }
                }
            }
        }
        /** 調(diào)用pool的getConnection()方法獲取連接*/
        return result.getConnection();
    }

getConnection方法邏輯不多,主要是調(diào)用了HikariPool的getConnection()方法凌蔬,而HikariDataSource中有兩個HikariPool對象,一個是fastPathPool是在HikariPool有參構造函數(shù)中創(chuàng)建, 如果沒有創(chuàng)建fastPathPool,那么就會在getConnection方法時創(chuàng)建pool對象露懒。

很顯然pool對象是由volatile關鍵字修飾的,而fastPathPool是final類型的砂心,所以fastPathPool的效率會比pool要高懈词,所以推薦使用HikariDataSource有參構造函數(shù)進行初始化。
2辩诞、由上可知獲取連接的邏輯是在HikariPool的getConnection方法中,繼續(xù)分析HikariPool的getConnection方法坎弯,源碼如下:

/** 獲取連接*/
    public Connection getConnection(final long hardTimeout) throws SQLException
    {
        /** 獲取鎖*/
        suspendResumeLock.acquire();
        final long startTime = currentTime();

        try {
            long timeout = hardTimeout;
            do {
                /** 從ConcurrentBag中借出一個PoolEntry對象 */
                PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
                if (poolEntry == null) {
                    break; // We timed out... break and throw exception
                }

                final long now = currentTime();
                /** 判斷連接是否被標記為拋棄 或者 空閑時間過長, 是的話就關閉連接*/
                if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection))) {
                    closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);
                    timeout = hardTimeout - elapsedMillis(startTime);
                }
                else {
                    metricsTracker.recordBorrowStats(poolEntry, startTime);
                    /** 通過Javassist創(chuàng)建代理連接*/
                    return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
                }
            } while (timeout > 0L);
            metricsTracker.recordBorrowTimeoutStats(startTime);
            throw createTimeoutException(startTime);
        }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
        }
        finally {
            /** 釋放鎖*/
            suspendResumeLock.release();
        }
    }

核心步驟只有兩步,一個是調(diào)用ConcurrentBag的borrow方法借用一個PoolEntry對象译暂,第二步調(diào)用調(diào)用PoolEntry的createProxyConnection方法動態(tài)生成代理connection對象抠忘。

這里涉及到了兩個核心的類,分別是ConcurrentBag和PoolEntry外永。
PoolEntry
PoolEntry顧名思義是連接池的節(jié)點崎脉,實際也可以看作是一個Connection對象的封裝,連接池中存儲的連接就是以PoolEntry的方式進行存儲伯顶。

PoolEntry內(nèi)部屬性如下:

屬性 類型 描述
connection Connection 數(shù)據(jù)庫連接
lastAccessed long 上一次訪問時間
lastBorrowed long 上一次借出時間
state volatile int 當前狀態(tài)
evict volatile boolean 是否該丟棄
openStatements FastList 打開的statement集合
hikariPool HikariPool 關聯(lián)的HikariPool對象
isReadOnly boolean 是否只讀
isAutoCommit boolean 是否自動提交

ConcurrentBag
ConcurrentBag直意就是并發(fā)包囚灼,本質(zhì)就是連接池的主體,存儲連接的封裝對象PoolEntry祭衩,另外做了并發(fā)控制來解決連接池的并發(fā)問題啦撮。

ConcurrentBag的內(nèi)部屬性如下:

image.png

ConcurrentBag借出一個元素

/** 借出一個對象 */
    public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
    {
        /** 1.從ThreadLocal中獲取當前線程綁定的對象集合 */
        final List<Object> list = threadList.get();
        /** 1.1.如果當前線程變量中存在就直接從list中返回一個*/
        for (int i = list.size() - 1; i >= 0; i--) {
            final Object entry = list.remove(i);
            @SuppressWarnings("unchecked")
            final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;
            if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
                return bagEntry;
            }
        }

        /** 2.當前等待對象數(shù)量自增1 */
        final int waiting = waiters.incrementAndGet();
        try {
            /** 3.遍歷當前緩存的sharedList, 如果當前狀態(tài)為未使用,則通過CAS修改為已使用*/
            for (T bagEntry : sharedList) {
                if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
                    /** 4.如果當前等待線程不止1個,則給監(jiān)聽中添加一個任務 */
                    if (waiting > 1) {
                        listener.addBagItem(waiting - 1);
                    }
                    return bagEntry;
                }
            }

            /** 4.如果當前緩存的sharedList為空或者都在使用中,那么給listener添加一個任務*/
            listener.addBagItem(waiting);

            timeout = timeUnit.toNanos(timeout);
            do {
                final long start = currentTime();
                /** 5.從阻塞隊列中等待超時獲取元素 */
                final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
                /** 6.如果獲取元素失敗或者獲取元素且使用成功則均返回 */
                if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
                    return bagEntry;
                }

                timeout -= elapsedNanos(start);
            } while (timeout > 10_000);

            return null;
        }
        finally {
            /** 6.等待線程數(shù)自減1 */
            waiters.decrementAndGet();
        }
    }

sharedList
從ThreadLocal中獲取連接失敗之后,會再次嘗試從sharedList中獲取汪厨,sharedList集合存在初始化的PoolEntry赃春。在ConcurrentBag初始化的,會初始化指定數(shù)量的PoolEntry對象存入sharedList劫乱,源碼如下:

/** ConcurrentHand
     * IBagStateListener bag狀態(tài)監(jiān)聽器,HikariPool實現(xiàn)了IBagStateListener接口
     * 所以構造器傳入的listener實際就是HikariPool對象
     * */
    public ConcurrentBag(final IBagStateListener listener)
    {
        this.listener = listener;
        //是否使用弱引用
        this.weakThreadLocals = useWeakThreadLocals();
        //初始化阻塞隊列
        this.handoffQueue = new SynchronousQueue<>(true);
        //初始化等待連接數(shù)
        this.waiters = new AtomicInteger();
        //初始化sharedList
        this.sharedList = new CopyOnWriteArrayList<>();
        if (weakThreadLocals) {
            this.threadList = ThreadLocal.withInitial(() -> new ArrayList<>(16));
        }
        else {
            this.threadList = ThreadLocal.withInitial(() -> new FastList<>(IConcurrentBagEntry.class, 16));
        }
    }

HikariPool內(nèi)部屬性包含了ConcurrentBag對象,在HikariPool初始化時會創(chuàng)建ConcurrentBag對象,所以ConcurrentBag的構造函數(shù)是在HikariPool初始化時調(diào)用织中,HikariPool構造函數(shù)如下:

public HikariPool(final HikariConfig config)
    {
        super(config);

        //初始化ConcurrentBag對象
        this.connectionBag = new ConcurrentBag<>(this);
        //創(chuàng)建SuspendResumeLock對象
        this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
        /** 初始化線程池,houseKeeping可以理解為保持空間充足的意思,空間也就是連接池,該線程池的作用就是保持連接池中合適的連接數(shù)的作用 */
        this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();

        /** 設置屬性*/
        checkFailFast();

        if (config.getMetricsTrackerFactory() != null) {
            setMetricsTrackerFactory(config.getMetricsTrackerFactory());
        }
        else {
            setMetricRegistry(config.getMetricRegistry());
        }

        setHealthCheckRegistry(config.getHealthCheckRegistry());

        handleMBeans(this, true);

        ThreadFactory threadFactory = config.getThreadFactory();
        /** 根據(jù)配置的最大連接數(shù),創(chuàng)建鏈表類型阻塞隊列 */
        LinkedBlockingQueue<Runnable> addQueue = new LinkedBlockingQueue<>(config.getMaximumPoolSize());
        this.addConnectionQueue = unmodifiableCollection(addQueue);
        /** 初始化創(chuàng)建連接線程池*/
        this.addConnectionExecutor = createThreadPoolExecutor(addQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardPolicy());
        /** 初始化關閉連接線程池*/
        this.closeConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());

        this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
        /** 創(chuàng)建保持連接池連接數(shù)量的任務*/
        this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);

        if (Boolean.getBoolean("com.zaxxer.hikari.blockUntilFilled") && config.getInitializationFailTimeout() > 1) {
            addConnectionExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
            addConnectionExecutor.setMaximumPoolSize(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);
        }
    }

這里有一個定時任務houseKeeperTask,該定時任務的作用是定時檢測連接池中連接的數(shù)量衷戈,執(zhí)行的內(nèi)容就是HouseKeep的run方法狭吼,邏輯如下:

private final class HouseKeeper implements Runnable
    {
        private volatile long previous = plusMillis(currentTime(), -housekeepingPeriodMs);

        @Override
        public void run()
        {
            try {
                /** 讀取連接池配置 */
                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  ";

                    /** 獲取當前連接池中已經(jīng)不是使用中的連接集合 */
                    final List<PoolEntry> notInUse = connectionBag.values(STATE_NOT_IN_USE);
                    int toRemove = notInUse.size() - config.getMinimumIdle();
                    for (PoolEntry entry : notInUse) {
                        /** 當前空閑的連接如果超過最大空閑時間idleTimeout則關閉空閑連接 */
                        if (toRemove > 0 && elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {
                            closeConnection(entry, "(connection has passed idleTimeout)");
                            toRemove--;
                        }
                    }
                }

                logPoolState(afterPrefix);
                /** 填充連接池,保持連接池數(shù)量至少保持minimum個連接數(shù)量 */
                fillPool(); // Try to maintain minimum connections
            }
            catch (Exception e) {
                logger.error("Unexpected exception in housekeeping task", e);
            }
        }
    }

該定時任務主要是為了維護連接池中連接的數(shù)量,首先需要將被標記為需要丟棄的連接進行關閉殖妇,然后將空閑超時的連接進行關閉刁笙,最后當連接池中的連接少于最小值時就需要對連接池進行補充連接的操作。所以在初始化連接池時,初始化連接的操作就是在fillPool方法中實現(xiàn)的疲吸。fillPool方法源碼如下:

    /** 填充連接池 */
    private synchronized void fillPool()
    {
        /**
         *  計算需要添加的連接數(shù)量
         *  config.getMaximumPoolSize - getTotalConnections() 表示連接池最大值-當前連接的數(shù)量=最多還可以創(chuàng)建的連接數(shù)
         *  config.getMinimumIdle() - getIdleConnections() 表示連接池最小值 - 當前空閑的連接數(shù)= 當前可以連接數(shù)
         *  Math.min計算得到最少需要的連接數(shù) - addConnectionQueue.size() = 還需要創(chuàng)建連接的任務數(shù)量
         * */
        final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())
                - addConnectionQueue.size();
        for (int i = 0; i < connectionsToAdd; i++) {
            /** 向創(chuàng)建連接線程池中提交創(chuàng)建連接的任務 */
            addConnectionExecutor.submit((i < connectionsToAdd - 1) ? poolEntryCreator : postFillPoolEntryCreator);
        }
    }

先計算需要創(chuàng)建的連接數(shù)量座每,向創(chuàng)建連接的線程池中提交任務 poolEntryCreator,創(chuàng)建最后一個任務時創(chuàng)建的是postFillPoolEntryCreator, 兩者沒有本質(zhì)的區(qū)別摘悴,只是打印的日志不一樣而已.

PoolEntryCreator創(chuàng)建PoolEntry對象的邏輯如下:

/** 創(chuàng)建PoolEntry對象線程 */
    private final class PoolEntryCreator implements Callable<Boolean> {
        /**
         * 日志前綴
         */
        private final String loggingPrefix;

        PoolEntryCreator(String loggingPrefix) {
            this.loggingPrefix = loggingPrefix;
        }

        @Override
        public Boolean call() {
            long sleepBackoff = 250L;
            /** 1.當前連接池狀態(tài)正常并且需求創(chuàng)建連接時 */
            while (poolState == POOL_NORMAL && shouldCreateAnotherConnection()) {
                /** 2.創(chuàng)建PoolEntry對象 */
                final PoolEntry poolEntry = createPoolEntry();
                if (poolEntry != null) {
                    /** 3.將PoolEntry對象添加到ConcurrentBag對象中的sharedList中 */
                    connectionBag.add(poolEntry);
                    logger.debug("{} - Added connection {}", poolName, poolEntry.connection);
                    if (loggingPrefix != null) {
                        logPoolState(loggingPrefix);
                    }
                    return Boolean.TRUE;
                }
                /** 睡眠指定時間*/
                quietlySleep(sleepBackoff);
                sleepBackoff = Math.min(SECONDS.toMillis(10), Math.min(connectionTimeout, (long) (sleepBackoff * 1.5)));
            }
            // Pool is suspended or shutdown or at max size
            return Boolean.FALSE;
        }
    }

createPoolEntry方法邏輯如下:

    /** 創(chuàng)建PoolEntry對象 */
    private PoolEntry createPoolEntry()
    {
        try {
            /** 1.初始化PoolEntry對象,會先創(chuàng)建Connection對象傳入PoolEntry的構造函數(shù)中 */
            final PoolEntry poolEntry = newPoolEntry();
            /** 2.獲取連接最大生命周期時長 */
            final long maxLifetime = config.getMaxLifetime();
            if (maxLifetime > 0) {
                /** 3.獲取一個隨機值,防止PoolEntry同時創(chuàng)建同時被銷毀,添加隨機值錯開時間差 */
                final long variance = maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;
                final long lifetime = maxLifetime - variance;
                /** 4.給PoolEntry添加定時任務,當PoolEntry對象達到最大生命周期時間后觸發(fā)定時任務將連接標記為被拋棄 */
                poolEntry.setFutureEol(houseKeepingExecutorService.schedule(
                        () -> {
                            /** 5.達到最大生命周期,拋棄連接 */
                            if (softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false /* not owner */)) {
                                /** 6.丟棄一個連接之后,調(diào)用addBagItem補充新的PoolEntry對象 */
                                addBagItem(connectionBag.getWaitingThreadCount());
                            }
                        },
                        lifetime, MILLISECONDS));
            }

            return poolEntry;
        }
        /** 異常捕獲*/
        catch (ConnectionSetupException e) {
            if (poolState == POOL_NORMAL) { // we check POOL_NORMAL to avoid a flood of messages if shutdown() is running concurrently
                logger.error("{} - Error thrown while acquiring connection from data source", poolName, e.getCause());
                lastConnectionFailure.set(e);
            }
            return null;
        }
        catch (SQLException e) {
            if (poolState == POOL_NORMAL) { // we check POOL_NORMAL to avoid a flood of messages if shutdown() is running concurrently
                logger.debug("{} - Cannot acquire connection from data source", poolName, e);
                lastConnectionFailure.set(new ConnectionSetupException(e));
            }
            return null;
        }
        catch (Exception e) {
            if (poolState == POOL_NORMAL) { // we check POOL_NORMAL to avoid a flood of messages if shutdown() is running concurrently
                logger.error("{} - Error thrown while acquiring connection from data source", poolName, e);
                lastConnectionFailure.set(new ConnectionSetupException(e));
            }
            return null;
        }
    }

創(chuàng)建數(shù)據(jù)庫連接

  // DriverDataSource 
  @Override
   public Connection getConnection() throws SQLException
   {
      return driver.connect(jdbcUrl, driverProperties);
   }
// NonRegisteringDriver
public Connection connect(String url, Properties info) throws SQLException {
        try {
            try {
                if (!ConnectionUrl.acceptsUrl(url)) {
                    return null;
                } else {
                    ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);
                    switch(conStr.getType()) {
                    case SINGLE_CONNECTION:
                        return ConnectionImpl.getInstance(conStr.getMainHost());
                    case FAILOVER_CONNECTION:
                    case FAILOVER_DNS_SRV_CONNECTION:
                        return FailoverConnectionProxy.createProxyInstance(conStr);
                    case LOADBALANCE_CONNECTION:
                    case LOADBALANCE_DNS_SRV_CONNECTION:
                        return LoadBalancedConnectionProxy.createProxyInstance(conStr);
                    case REPLICATION_CONNECTION:
                    case REPLICATION_DNS_SRV_CONNECTION:
                        return ReplicationConnectionProxy.createProxyInstance(conStr);
                    default:
                        return null;
                    }
                }
            } catch (UnsupportedConnectionStringException var5) {
                return null;
            } catch (CJException var6) {
                throw (UnableToConnectException)ExceptionFactory.createException(UnableToConnectException.class, Messages.getString("NonRegisteringDriver.17", new Object[]{var6.toString()}), var6);
            }
        } catch (CJException var7) {
            throw SQLExceptionsMapping.translateException(var7);
        }
    }

首先創(chuàng)建一個新的PoolEntry對象峭梳,PoolEntry構造時會創(chuàng)建Connection對象,另外如果連接設置了最大生命周期時長蹂喻,那么需要給每個PoolEntry添加定時任務葱椭,為了防止多個PoolEntry同時創(chuàng)建同時被關閉,所以每個PoolEntry的最大生命周期時間都不一樣口四。當PoolEntry達到最大生命周期后會觸發(fā)softEvictConnection方法孵运,將PoolEntry標記為需要被丟棄,另外由于拋棄了PoolEntry對象蔓彩,所以需要重新調(diào)用addBagItem方法對PoolEntry對象進行補充治笨。
通過IBagStateListener創(chuàng)建新的元素
由于第二步可知,IBagStateListener主要有一個addBagItem方法粪小,HikariPool實現(xiàn)了addBagItem方法,方法源碼如下:

public void addBagItem(final int waiting)
    {
        /** 判斷是否需要創(chuàng)建連接 */
        final boolean shouldAdd = waiting - addConnectionQueue.size() >= 0; // Yes, >= is intentional.
        if (shouldAdd) {
            /** 向創(chuàng)建連接線程池中提交創(chuàng)建連接的任務 */
            addConnectionExecutor.submit(poolEntryCreator);
        }
    }

從ConcurrentBag中獲取連接一共分成三步抡句,首先從當前線程的ThreadLocal中獲取探膊,如果有直接返回一個連接,如果ThreadLocal中沒有則從sharedList中獲取待榔,sharedList可以理解為ConcurrentBag緩存的連接池逞壁,每當創(chuàng)建了一個PoolEntry對象之后都會添加到sharedList中去,如果sharedList中的連接狀態(tài)都不是可用狀態(tài)锐锣,此時就需要通過IBagStateListener提交一個創(chuàng)建連接的任務腌闯,交給創(chuàng)建連接的線程池去執(zhí)行,創(chuàng)建新的連接雕憔。

新的連接創(chuàng)建成功之后會將PoolEntry對象添加到無容量的阻塞隊列handoffQueue中姿骏,等待連接的線程不斷嘗試從handoffQueue隊列中獲取連接直到成功獲取或者超時返回。

3.2 釋放連接

當客戶端釋放連接時會調(diào)用collection的close方法斤彼,Hikari中的Connection使用的是代理連接ProxyConnection對象分瘦,調(diào)用close方法時會調(diào)用關聯(lián)的PoolEntry對象的回收方法recycle方法,PoolEntry的recycle方法源碼如下:

/**
    * Release this entry back to the pool.
    *
    * @param lastAccessed last access time-stamp
    */
   void recycle(final long lastAccessed)
   {
      if (connection != null) {
         this.lastAccessed = lastAccessed;
         hikariPool.recycle(this);
      }
   }
/**
    * Recycle PoolEntry (add back to the pool)
    *
    * @param poolEntry the PoolEntry to recycle
    */
   @Override
   void recycle(final PoolEntry poolEntry)
   {
      metricsTracker.recordConnectionUsage(poolEntry);

      connectionBag.requite(poolEntry);
   }
/**
    * This method will return a borrowed object to the bag.  Objects
    * that are borrowed from the bag but never "requited" will result
    * in a memory leak.
    *
    * @param bagEntry the value to return to the bag
    * @throws NullPointerException if value is null
    * @throws IllegalStateException if the bagEntry was not borrowed from the bag
    */
   public void requite(final T bagEntry)
   {
      bagEntry.setState(STATE_NOT_IN_USE);

      for (int i = 0; waiters.get() > 0; i++) {
         if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
            return;
         }
         else if ((i & 0xff) == 0xff) {
            parkNanos(MICROSECONDS.toNanos(10));
         }
         else {
            yield();
         }
      }

      final List<Object> threadLocalList = threadList.get();
      threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
   }

回收連接最終會調(diào)用ConcurrentBag的requite方法琉苇,方法邏輯不復雜嘲玫,首先將PoolEntry元素狀態(tài)設置為未使用,然后判斷當前是否存在等待連接的線程并扇,如果存在則將連接加入到無界阻塞隊列中去去团,由等待連接的線程從阻塞隊列中去獲取;

如果當前沒有等待連接的線程土陪,則將連接添加到本地線程變量ThreadLocal中昼汗,等待當前線程下次獲取連接時直接從ThreadLocal中獲取。

4. Hikari連接池高性能的原因

1旺坠、采用自定義的FastList替代了ArrayList乔遮,F(xiàn)astList的get方法去除了范圍檢查rangeCheck邏輯,并且remove方法是從尾部開始掃描的取刃,而并不是從頭部開始掃描的蹋肮。因為Connection的打開和關閉順序通常是相反的

2、初始化時創(chuàng)建了兩個HikariPool對象璧疗,一個采用final類型定義坯辩,避免在獲取連接時才初始化,因為獲取連接時才初始化就需要做同步處理

3崩侠、Hikari創(chuàng)建連接是通過javassist動態(tài)字節(jié)碼生成技術創(chuàng)建的漆魔,性能更好

4、從連接池中獲取連接時對于同一個線程在threadLocal中添加了緩存却音,同一線程獲取連接時沒有并發(fā)操作

5改抡、Hikari最大的特點是在高并發(fā)的情況下盡量的減少鎖競爭

參考鏈接

數(shù)據(jù)庫連接池之Hikari源碼解析(轉(zhuǎn))

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市系瓢,隨后出現(xiàn)的幾起案子阿纤,更是在濱河造成了極大的恐慌,老刑警劉巖夷陋,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件欠拾,死亡現(xiàn)場離奇詭異,居然都是意外死亡骗绕,警方通過查閱死者的電腦和手機藐窄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酬土,“玉大人荆忍,你說我怎么就攤上這事〕方桑” “怎么了东揣?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長腹泌。 經(jīng)常有香客問我嘶卧,道長,這世上最難降的妖魔是什么凉袱? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任芥吟,我火速辦了婚禮侦铜,結果婚禮上,老公的妹妹穿的比我還像新娘钟鸵。我一直安慰自己钉稍,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布棺耍。 她就那樣靜靜地躺著贡未,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蒙袍。 梳的紋絲不亂的頭發(fā)上俊卤,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音害幅,去河邊找鬼消恍。 笑死,一個胖子當著我的面吹牛以现,可吹牛的內(nèi)容都是我干的狠怨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼邑遏,長吁一口氣:“原來是場噩夢啊……” “哼佣赖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起记盒,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤憎蛤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后孽鸡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹂午,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡栏豺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年彬碱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奥洼。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡巷疼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灵奖,到底是詐尸還是另有隱情嚼沿,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布瓷患,位于F島的核電站骡尽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏擅编。R本人自食惡果不足惜攀细,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一箫踩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谭贪,春花似錦境钟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至套媚,卻和暖如春缚态,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凑阶。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工猿规, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宙橱。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓姨俩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親师郑。 傳聞我的和親對象是個殘疾皇子环葵,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容