欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot?HikariCP配置項(xiàng)及源碼解析

 更新時(shí)間:2023年02月02日 11:58:05   作者:MrDong先生  
這篇文章主要為大家介紹了SpringBoot?HikariCP配置項(xiàng)及源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

在SpringBoot2.0之后,采用的默認(rèn)數(shù)據(jù)庫(kù)連接池就是Hikari,是一款非常強(qiáng)大,高效,并且號(hào)稱(chēng)“史上最快連接池”。我們知道的連接池有C3P0,DBCP,Druid它們都比較成熟穩(wěn)定,但性能不是十分好。

我們?cè)谌粘5木幋a中,通常會(huì)將一些對(duì)象保存起來(lái),這主要考慮的是對(duì)象的創(chuàng)建成本;比如像線(xiàn)程資源、數(shù)據(jù)庫(kù)連接資源或者 TCP 連接等,這類(lèi)對(duì)象的初始化通常要花費(fèi)比較長(zhǎng)的時(shí)間,如果頻繁地申請(qǐng)和銷(xiāo)毀,就會(huì)耗費(fèi)大量的系統(tǒng)資源,造成不必要的性能損失,于是在Java 中,池化技術(shù)應(yīng)用非常廣泛。在軟件行開(kāi)發(fā)中,軟件的性能是占主導(dǎo)地位的,于是HikariCP就在眾多數(shù)據(jù)庫(kù)連接池中脫穎而出。

為什么HikariCP性能高

  • 優(yōu)化代理和攔截器:減少代碼。
  • 字節(jié)碼精簡(jiǎn) :優(yōu)化代碼(HikariCP利用了一個(gè)第三方的Java字節(jié)碼修改類(lèi)庫(kù)Javassist來(lái)生成委托實(shí)現(xiàn)動(dòng)態(tài)代理,動(dòng)態(tài)代理的實(shí)現(xiàn)在ProxyFactory類(lèi)),直到編譯后的字節(jié)碼最少,這樣,CPU緩存可以加載更多的程序代碼。
  • 通過(guò)代碼設(shè)計(jì)和優(yōu)化大幅減少線(xiàn)程間的鎖競(jìng)爭(zhēng)。這點(diǎn)主要通過(guò)ConcurrentBag來(lái)實(shí)現(xiàn)。
  • 自定義數(shù)組類(lèi)型(FastStatementList)代替ArrayList:避免每次get()調(diào)用都要進(jìn)行range check,避免調(diào)用remove()時(shí)的從頭到尾的掃描,相對(duì)與ArrayList極大地提升了性能,而其中的區(qū)別是,ArrayList在每次執(zhí)行get(Index)方法時(shí),都需要對(duì)List的范圍進(jìn)行檢查,而FastStatementList不需要,在能確保范圍的合法性的情況下,可以省去范圍檢查的開(kāi)銷(xiāo)。自定義集合類(lèi)型(ConcurrentBag):支持快速插入和刪除,特別是在同一線(xiàn)程既添加又刪除項(xiàng)時(shí),提高并發(fā)讀寫(xiě)的效率;
  • 關(guān)于Connection的操作:另外在Java代碼中,很多都是在使用完之后直接關(guān)閉連接,以前都是從頭到尾遍歷,來(lái)關(guān)閉對(duì)應(yīng)的Connection,而HikariCP則是從尾部對(duì)Connection集合進(jìn)行掃描,整體上來(lái)說(shuō),從尾部開(kāi)始的性能更好一些。
  • 針對(duì)連接中斷的情況:比其他CP響應(yīng)時(shí)間上有了極好的優(yōu)化,響應(yīng)時(shí)間為5S,會(huì)拋出SqlException異常,并且后續(xù)的getConnection()可以正常進(jìn)行

下面為大家附上一張官方的性能測(cè)試圖,我們可以從圖上很直觀的看出HikariCP的性能卓越:

常用配置項(xiàng)

autoCommit

控制從池返回的連接的默認(rèn)自動(dòng)提交行為,默認(rèn)為true

connectionTimeout

控制客戶(hù)端等待來(lái)自池的連接的最大毫秒數(shù)。

如果在沒(méi)有連接可用的情況下超過(guò)此時(shí)間,則將拋出 SQLException??山邮艿淖畹瓦B接超時(shí)時(shí)間為 250 毫秒。默認(rèn)值:30000(30 秒)

idleTimeout

連接允許在池中閑置的最長(zhǎng)時(shí)間

如果idleTimeout+1秒>maxLifetime 且 maxLifetime>0,則會(huì)被重置為0(代表永遠(yuǎn)不會(huì)退出);如果idleTimeout!=0且小于10秒,則會(huì)被重置為10秒

這是HikariCP用來(lái)判斷是否應(yīng)該從連接池移除空閑連接的一個(gè)重要的配置。負(fù)責(zé)剔除的也還是HouseKeeper這個(gè)定時(shí)任務(wù),值為0時(shí),HouseKeeper不會(huì)移除空閑連接,直到到達(dá)maxLifetime后,才會(huì)移除,默認(rèn)值也就是0。
正常情況下,HouseKeeper會(huì)找到所有狀態(tài)為空閑的連接隊(duì)列,遍歷一遍,將空閑超時(shí)到達(dá)idleTimeout且未超過(guò)minimumIdle數(shù)量的連接的批量移除。

maxLifetime

池中連接最長(zhǎng)生命周期;如果不等于0且小于30秒則會(huì)被重置回30分鐘

了解這個(gè)值的作用前,先了解一下MySQLwait_timeout的作用:MySQL 為了防止空閑連接浪費(fèi),占用資源,在超過(guò)wait_timeout時(shí)間后,會(huì)主動(dòng)關(guān)閉該連接,清理資源;默認(rèn)是28800s,也就是8小時(shí)。簡(jiǎn)而言之就是MySQL會(huì)在某個(gè)連接超過(guò)8小時(shí)還沒(méi)有任何請(qǐng)求時(shí)自動(dòng)斷開(kāi)連接,但是HikariCP如何知道池子里的連接有沒(méi)有超過(guò)這個(gè)時(shí)間呢?所以就有了maxLifetime,配置后HikariCP會(huì)把空閑鏈接超過(guò)這個(gè)時(shí)間的給剔除掉,防止獲取到已經(jīng)關(guān)閉的連接導(dǎo)致異常。

connectionTestQuery

將在從池中向您提供連接之前執(zhí)行的查詢(xún),以驗(yàn)證與數(shù)據(jù)庫(kù)的連接是否仍然有效,如select 1

minimumIdle

池中維護(hù)的最小空閑連接數(shù);minIdle<0或者minIdle>maxPoolSize,則被重置為maxPoolSize

HikariCP Pool創(chuàng)建時(shí),會(huì)啟動(dòng)一個(gè)HouseKeeper定時(shí)任務(wù),每隔30s,判斷空閑線(xiàn)程數(shù)低于minimumIdle,并且當(dāng)前線(xiàn)程池總連接數(shù)小于maximumPoolSize,就建立和MySQL的一個(gè)長(zhǎng)連接,然后加入到連接池中。官方建議minimumIdlemaximumPoolSize保持一致。 因?yàn)?code>HikariCP的HouseKeeper在發(fā)現(xiàn)idleTimeout>0 并且 minimumIdle < maximumPoolSize時(shí),先會(huì)去掃描一遍需要移除空閑連接,和MySQL斷開(kāi)連接。然后再一次性補(bǔ)滿(mǎn)空閑連接數(shù)至到minimumIdle

maximumPoolSize

池中最大連接數(shù),其實(shí)就是線(xiàn)程池中隊(duì)列的大小,默認(rèn)大小為10(包括閑置和使用中的連接)

如果maxPoolSize小于1,則會(huì)被重置。當(dāng)minIdle<=0被重置為DEFAULT_POOL_SIZE則為10;如果minIdle>0則重置為minIdle的值

HikariCP架構(gòu)

分析源碼之前,先給大家介紹一下HikariCP的整體架構(gòu),整體架構(gòu)和DBCP2 的有點(diǎn)類(lèi)似(由此可見(jiàn) HikariCP 與 DBCP2 性能差異并不是由于架構(gòu)設(shè)計(jì)),下面我總結(jié)了幾點(diǎn),來(lái)和大家一起探討下:

  • HikariCP通過(guò)JMX調(diào)用HikariPoolMXBean來(lái)獲取連接池的連接數(shù)、獲取等待連接的線(xiàn)程數(shù)、丟棄未使用連接、掛起和恢復(fù)連接池等。
  • HikariCP通過(guò)JMX調(diào)用HikariConfigMXBean來(lái)動(dòng)態(tài)修改配置。
  • HikariCP使用HikariConfig加載配置文件,一般會(huì)作為入?yún)?lái)構(gòu)造 HikariDataSource 對(duì)象。
  • HikariPool是一個(gè)非常重要的類(lèi),它負(fù)責(zé)管理連接,涉及到比較多的代碼邏輯。
  • HikariDataSource主要用于操作HikariPool獲取連接。
  • ConcurrentBag用于優(yōu)化大幅減少線(xiàn)程間的鎖競(jìng)爭(zhēng)。
  • PoolBaseHikariPool的父類(lèi),主要負(fù)責(zé)操作實(shí)際的DataSource獲取連接,并設(shè)置連接的一些屬性。

源碼解析

HikariConfig

HikariConfig保存了所有連接池配置,另外實(shí)現(xiàn)了HikariConfigMXBean接口,有些配置可以利用JMX運(yùn)行時(shí)變更。核心配置項(xiàng)屬性會(huì)在下面給大家介紹,這邊Dong哥就簡(jiǎn)單介紹一下了。

HikariPool

getConnection

  public Connection getConnection(final long hardTimeout) throws SQLException
   {
      //這里是防止線(xiàn)程池處于暫停狀態(tài)(通常不允許線(xiàn)程池可暫停)
      suspendResumeLock.acquire();
      final long startTime = currentTime();
      try {
         long timeout = hardTimeout;
         do {
            //PoolEntry 用于跟蹤connection實(shí)例,里面包裝了Connection;
            //從connectionBag中獲取一個(gè)對(duì)象,并且檢測(cè)是否可用
            PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
            if (poolEntry == null) {
               break; // We timed out... break and throw exception
            }
            final long now = currentTime();
            //1、已被標(biāo)記為驅(qū)逐 2、已超過(guò)最大存活時(shí)間 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);
               //刷新超時(shí)時(shí)間
               timeout = hardTimeout - elapsedMillis(startTime);
            }
            else {
               metricsTracker.recordBorrowStats(poolEntry, startTime);
               return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
            }
            //如果沒(méi)超時(shí)則再次獲取
         } while (timeout > 0L);
		 //超時(shí)時(shí)間到仍未獲取到鏈接則拋出 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();
      }
   }

校驗(yàn)

  • isMarkedEvicted:檢查當(dāng)前鏈接是否已被驅(qū)逐
  • elapsedMillis(poolEntry.lastAccessed, now):檢查鏈接是否超過(guò)最大存活時(shí)間(maxLifetime配置時(shí)間)
/**
* startTime 上次使用時(shí)間
* endTime   當(dāng)前時(shí)間
*/
static long elapsedMillis(long startTime, long endTime) {
    return CLOCK.elapsedMillis0(startTime, endTime);
}
  • isConnectionAlive:連接是否還是存活狀態(tài)
 boolean isConnectionAlive(final Connection connection)
   {
      try {
         try {
            //如果支持Connection networkTimeout,則優(yōu)先使用并設(shè)置
            setNetworkTimeout(connection, validationTimeout);
            final int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;
	    //如果jdbc實(shí)現(xiàn)支持jdbc4 則使用jdbc4 Connection的isValid方法檢測(cè)
            if (isUseJdbc4Validation) {
               return connection.isValid(validationSeconds);
            }
	    //查詢(xún)數(shù)據(jù)庫(kù)檢測(cè)連接可用性
            try (Statement statement = connection.createStatement()) {
              //如果不支持Connection networkTimeout 則設(shè)置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());
         //捕獲到異常,說(shuō)明鏈接不可用。(connection is unavailable)
         return false;
      }
   }

HouseKeeper

HouseKeeper負(fù)責(zé)保持,我們始終有minimumIdle空閑鏈接可用

 private final class HouseKeeper implements Runnable
   {
      //默認(rèn)30s,執(zhí)行一次
      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  ";
	       //空閑鏈接數(shù)
               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)) {
                     //關(guān)閉過(guò)多的空閑超時(shí)鏈接
                     closeConnection(entry, "(connection has passed idleTimeout)");
                     toRemove--;
                  }
               }
            }
	    //記錄pool狀態(tài)信息
            logPoolState(afterPrefix);
	    //補(bǔ)充空閑鏈接
            fillPool(); 
         }
         catch (Exception e) {
            LOGGER.error("Unexpected exception in housekeeping task", e);
         }
      }
   }

HouseKeeper其實(shí)是一個(gè)線(xiàn)程,也是寫(xiě)在HikariPool類(lèi)里面的一個(gè)內(nèi)部類(lèi),主要負(fù)責(zé)保持 minimumIdle 的空閑鏈接。HouseKeeper也用到了validationTimeout, 并且會(huì)根據(jù)minimumIdle配置,通過(guò)fill 或者 remove保持最少空閑鏈接數(shù)。
HouseKeeper線(xiàn)程初始化:

 public HikariPool(final HikariConfig config)
   {
      super(config);
      this.connectionBag = new ConcurrentBag<>(this);
      this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
      //執(zhí)行初始化
      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類(lèi)的子類(lèi),Java推薦僅在開(kāi)發(fā)定時(shí)任務(wù)程序時(shí)采用ScheduledThreadPoolExecutor類(lèi)
            ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, threadFactory, new DiscardPolicy());
            //傳入false,則執(zhí)行shutdown()方法之后,待處理的任務(wù)將不會(huì)被執(zhí)行
            executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
            //取消任務(wù)后,判斷是否需要從阻塞隊(duì)列中移除任務(wù)
            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修飾,構(gòu)造時(shí)決定,如果使用無(wú)參構(gòu)造為null,使用有參構(gòu)造和pool一樣
   private final HikariPool fastPathPool;
   //volatile修飾,無(wú)參構(gòu)造不會(huì)設(shè)置pool,在getConnection時(shí)構(gòu)造pool,有參構(gòu)造和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();
   }
}

以上就是SpringBoot HikariCP配置項(xiàng)及源碼解析的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot HikariCP配置的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論