1. 程式人生 > 其它 >Spring Boot整合資料庫連線池 HikariCP

Spring Boot整合資料庫連線池 HikariCP

目錄

摘要:淺談連線池基本概念和工作原理、常見資料庫連線池效能對比、HiKariCP速度為什麼快和常見屬性對比。最後給出一個Spring Boot整合HiKariCP的入門案例。

§工程環境

  • JDK:1.8.0_231
  • maven:3.6.1
  • Apache Tomcat:9.0.46
  • Spring Boot: 2.5.0
  • mysql-connector-java:8.0.25
  • mysql:8.0.25
  • HikariCP:4.0.3

§資料庫連線池介紹

  一個普通的java程式,要查詢資料庫的資料,基本流程是這樣的:

  可以看到:進行一次查詢,要進行很多次網路互動,這樣的缺點是:

  1. 網路IO多

  2. 響應時間長,導致QPS降低

  3. 頻繁建立連線和關閉連線,浪費資料庫資源,影響伺服器效能

  因為TCP連線的建立開支十分昂貴,並且資料庫所能承載的TCP併發連線數也有限制,針對這種場景,資料庫連線池應運而生。資料庫連線池是用於建立、管理和釋放資料庫連線的緩衝池技術,緩衝池中的連線可以被任何需要它們的執行緒使用。當一個執行緒需要用JDBC對一個數據庫操作時,將從池中請求一個連線;當這個連線使用完畢後,將返回到連線池中,等待其它執行緒的排程。

  這裡用到了池化技術,如大家屢見不鮮的執行緒池、整數池、字串池、物件池和Http 連線池等等,都是對這個思想的應用。池化技術的思想主要是通過複用物件,以減少每次獲取資源時建立和釋放所帶來的資源消耗,提高資源利用率,這是典型的以空間換取時間的策略。

  資料庫連線池負責分配、管理、釋放資料庫連線,它允許應用服務重複使用資料庫連線,而非重新建立。使用連線池之後,流程是這樣的:

  由此可見,資料庫連線的建立和關閉連線均由連線池來實現。這樣的機制有如下兩個優點:

  1. 封裝關於資料庫訪問的各種引數,實現統一管理
  2. 通過對資料庫的連線池管理,減少網路開銷並提升資料庫效能

資料庫連線池工作原理剖析

  資料庫連線池的工作原理主要由三部分組成,分別為

  • 連線池的建立
  • 連線池的管理
  • 連線池的關閉
  1. 連線池的建立。應用初始化時,根據配置的最小連線數,在連線池將建立此數目的資料庫連線放到池中,以便使用時能從連線池中獲取。連線池中的連線不能隨意建立和關閉,這樣避免了連線隨意建立和關閉造成的系統開銷。

    Java中提供了很多容器類可以方便的構建連線池,例如Vector、Stack等。

  2. 連線池的管理。連線池管理策略是連線池機制的核心,連線池內連線的分配和釋放對系統的效能有很大的影響。其管理策略是:當客戶請求資料庫連線時,首先檢視連線池中是否有空閒連線,如果存在空閒連線,則將連線分配給客戶使用;如果沒有空閒連線,則檢視當前所開的連線數是否已經達到最大連線數,如果沒達到就重新建立一個連線給請求的客戶;如果達到就按設定的最大等待時間進行等待,如果超出最大等待時間,則丟擲異常給客戶。 當客戶釋放資料庫連線時,先判斷池中的連線數是否超過了設定的最大連線數,如果超過就從連線池中刪除該連線;否則,保留連線,等待再次使用。

  3. 連線池的關閉。應用程式關閉時,關閉連線池中所有連線,釋放所有相關資源。

  在Java這個自由開放的生態中,已經有非常多優秀的開源資料庫連線池可以供大家選擇,比如:DBCP、C3P0、Druid、HikariCP、tomcat-jdbc等。而在Spring Boot 2.x中,對資料來源的選擇也緊跟潮流,採用了目前效能最佳的HikariCP。接下來,我們就來具體聊聊HikariCP。

§Java常見資料庫連線池效能比較

  單從效能角度分析,效能從高到低依次是:HikariCP、druid、tomcat-jdbc、dbcp、c3p0。下圖是HikariCP官網給出的效能對比:

  從上圖中可以直觀的看出,Hikari 在 獲取和釋放 Connection 和 Statement 方法的 OPS 不是一般的高,那是相當的高,基本上是碾壓其他連線池,這裡就不一一點名了。除了 OPS 外,HikariCP 的穩定性也更好,效能毛刺更少。

§資料庫連線池選型 Druid vs HikariCP效能對比

  • 從功能角度考慮,Druid 功能更豐富,除具備連線池基本功能外,還支援sql級監控、擴充套件、SQL防注入等。最新版甚至有叢集監控。兩者的側重點不一樣。
  • 從效能角度考慮,從資料處理速度角度來看,HikariCP確實更強,但Druid由阿里巴巴背書,可支援”雙十一”等最嚴苛的使用場景,並且提供了強大的監控功能,在國內有不少使用者。不過,Spring Boot 2.x已經使用HikariCP作為預設的資料庫連線池,其優秀程度可見一斑。
  • 從監控角度考慮,如果我們有像skywalking、prometheus等元件是可以將監控能力交給這些的,HikariCP也可以將metrics暴露出去。

  HikariCP作為後起之秀,是目前最快的Java資料庫連線池。

§HikariCP為什麼這麼快

  HikariCP為什麼這麼快呢?是因為它在如下四個方面做了優化,以提升效能:

  1. 優化並精簡位元組碼。使用Java位元組碼修改類庫Javassist來生成委託實現動態代理,比JDK Proxy生成的位元組碼更少,精簡了很多不必要的位元組碼。
  2. 使用自定義的無鎖的、效能更好的併發集合類ConcurrentBag。
  3. 使用自定義的陣列型別FastList替代ArrayList。FastList是List介面的精簡實現。
  4. 優化代理和攔截器:減少程式碼,例如 HikariCP 的 Statement proxy 只有100行程式碼,只有 BoneCP 的十分之一;

  下面是FastList原始碼:

/**
 * ArrayList精簡版的、沒有列表檢查的 FastList
 *
 * @author Brett Wooldridge
 */
public final class FastList<T> implements List<T>, RandomAccess, Serializable{
  private static final long serialVersionUID = -4598088075242913858L;

  private final Class<?> clazz;
  private T[] elementData;
  private int size;

  /**
   * 構建一個預設大小為32的列表。
   * @param clazz the Class stored in the collection
   */
  @SuppressWarnings("unchecked")
  public FastList(Class<?> clazz) {
     this.elementData = (T[]) Array.newInstance(clazz, 32);
     this.clazz = clazz;
  }

  /**
   * 構造具有指定大小的列表。
   * @param clazz the Class stored in the collection
   * @param capacity the initial size of the FastList
   */
  @SuppressWarnings("unchecked")
  public FastList(Class<?> clazz, int capacity) {
     this.elementData = (T[]) Array.newInstance(clazz, capacity);
     this.clazz = clazz;
  }

@Override
   public boolean add(T element) {
      //給 list新增屬性
      //如果 size值小於 初始化的值
      if (size < elementData.length) {
         elementData[size++] = element;
      } else {
         // 溢位的程式碼
         //elementData 原始32不夠用 需要擴容
         final int oldCapacity = elementData.length;
         final int newCapacity = oldCapacity << 1;
         @SuppressWarnings("unchecked")
         //擴容集合
         final T[] newElementData = (T[]) Array.newInstance(clazz, newCapacity);
         //陣列複製
         System.arraycopy(elementData, 0, newElementData, 0, oldCapacity);
         //屬性賦值
         newElementData[size++] = element;
         elementData = newElementData;
      }

      return true;
   }
   /**
    * 貼出ArrayList的get程式碼,來看看為什麼 FastList 更快
    *  public E get(int index) {
    *   rangeCheck(index);
    *    return elementData(index);
    *   }
    *   ArrayList呼叫rangeCheck以檢查角標範圍,而FastList直接讀取元素,節約時間
    */
   @Override
   public T get(int index) {
      return elementData[index];
   }
   /**
    * 這個是ArrayList的 remove()程式碼, FastList 少了檢查範圍和從頭到尾的 檢查元素動作,速度更快
    *   rangeCheck(index);
    *   modCount++;
    *    E oldValue = elementData(index);
    */
   @Override
   public boolean remove(Object element) {
      for (int index = size - 1; index >= 0; index--) {
         if (element == elementData[index]) {
            final int numMoved = size - index - 1;
            //如果角標不是最後一個 copy一個新的陣列結構
            if (numMoved > 0) {
               System.arraycopy(elementData, index + 1, elementData, index, numMoved);
            }
            //如果角標是最後面的 直接初始化為null
            elementData[--size] = null;
            return true;
         }
      }
      return false;
   }

§資料來源配置詳解

  由於Spring Boot的自動化配置機制,大部分對於資料來源的配置都可以通過配置引數的方式去改變。只有一些特殊情況,比如:更換預設資料來源,多資料來源共存等情況才需要去修改覆蓋初始化的Bean內容。本節我們主要講Hikari的配置,所以對於使用其他資料來源或者多資料來源的情況,在之後的教程中學習。

  在Spring Boot自動化配置中,對於資料來源的配置可以分為兩類:

  • 通用配置:以spring.datasource.*的形式存在,主要是對一些即使使用不同資料來源也都需要配置的一些常規內容。比如:資料庫連結地址、使用者名稱、密碼等。這裡就不做過多說明了,通常就這些配置:
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

溫馨提示:driver-class-name用於指定JDBC驅動程式的類名,預設從jdbc url中自動探測。

com.mysql.jdbc.Driver 是 mysql-connector-java 5中的,com.mysql.cj.jdbc.Driver 是 mysql-connector-java 版本6以後的。

  • 資料來源連線池配置:以spring.datasource.<資料來源名稱>.*的形式存在,比如:Hikari的配置引數就是spring.datasource.hikari.*形式。下面這個是我們最常用的幾個配置項及對應說明:
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.idle-timeout=500000
spring.datasource.hikari.max-lifetime=540000
spring.datasource.hikari.connection-timeout=60000
spring.datasource.hikari.connection-test-query=SELECT 1

  這些配置的含義:

  • spring.datasource.hikari.minimum-idle: 最小空閒連線,預設值10,小於0或大於maximum-pool-size,都會重置為maximum-pool-size。

  • spring.datasource.hikari.maximum-pool-size: 最大連線數,小於等於0會被重置為預設值10;大於零小於1會被重置為minimum-idle的值

  • spring.datasource.hikari.idle-timeout: 空閒連線超時時間,此屬性控制允許連線在連線池中閒置的最長時間。預設值600000毫秒(10分鐘),大於等於max-lifetime且max-lifetime>0,會被重置為0;不等於0且小於10秒,會被重置為10秒。

    此設定僅適用於maximumPoolSize-minimumIdle的連線。一旦連線池達到最小連線數,空閒連線將不會退出。在超時之前,連線永遠不會退出。值為0意味著空閒連線永遠不會從池中刪除。允許的最小值是10000ms(10秒),預設值值是600000(10分鐘)。

  • spring.datasource.hikari.max-lifetime: 連線最大存活時間,不等於0且小於30秒,會被重置為預設值30分鐘.設定應該比mysql設定的超時時間短

  • spring.datasource.hikari.connection-timeout: 連線超時時間:毫秒,小於250ms,否則被重置為預設值30秒

  • spring.datasource.hikari.connection-test-query: 用於測試連線是否可用的查詢語句

更多完整配置項可查看下錶:

name 預設配置validate之後的值 構造器預設值 validate重置 描述
autoCommit TRUE TRUE 自動提交從池中返回的連線
connectionTimeout 30000 SECONDS.toMillis(30) = 30000 如果小於250毫秒,則被重置回30秒 等待來自池的連線的最大毫秒數
idleTimeout 600000 MINUTES.toMillis(10) = 600000 如果idleTimeout+1秒>maxLifetime 且 maxLifetime>0,則會被重置為0(代表永遠不會退出);如果idleTimeout!=0且小於10秒,則會被重置為10秒 連線允許在池中閒置的最長時間
maxLifetime 1800000 MINUTES.toMillis(30) = 1800000 如果不等於0且小於30秒則會被重置回30分鐘 池中連線最長生命週期
connectionTestQuery null null 如果您的驅動程式支援JDBC4,我們強烈建議您不要設定此屬性
minimumIdle 10 -1 minIdle<0或者minIdle>maxPoolSize,則被重置為maxPoolSize 池中維護的最小空閒連線數
maximumPoolSize 10 -1 如果maxPoolSize小於1,則會被重置。當minIdle<=0被重置為DEFAULT_POOL_SIZE則為10;如果minIdle>0則重置為minIdle的值 池中最大連線數,包括閒置和使用中的連線
metricRegistry null null 該屬性允許您指定一個 Codahale / Dropwizard MetricRegistry 的例項,供池使用以記錄各種指標
healthCheckRegistry null null 該屬性允許您指定池使用的Codahale / Dropwizard HealthCheckRegistry的例項來報告當前健康資訊
poolName HikariPool-1 null 連線池的使用者定義名稱,主要出現在日誌記錄和JMX管理控制檯中以識別池和池配置
initializationFailTimeout 1 1 如果池無法成功初始化連線,則此屬性控制池是否將 fail fast
isolateInternalQueries FALSE FALSE 是否在其自己的事務中隔離內部池查詢,例如連線活動測試
allowPoolSuspension FALSE FALSE 控制池是否可以通過JMX暫停和恢復
readOnly FALSE FALSE 從池中獲取的連線是否預設處於只讀模式
registerMbeans FALSE FALSE 是否註冊JMX管理Bean(MBeans)
catalog null driver default 為支援 catalog 概念的資料庫設定預設 catalog
connectionInitSql null null 該屬性設定一個SQL語句,在將每個新連線建立後,將其新增到池中之前執行該語句。
driverClassName null null HikariCP將嘗試通過僅基於jdbcUrl的DriverManager解析驅動程式,但對於一些較舊的驅動程式,還必須指定driverClassName
transactionIsolation null null 控制從池返回的連線的預設事務隔離級別
validationTimeout 5000 SECONDS.toMillis(5) = 5000 如果小於250毫秒,則會被重置回5秒 連線將被測試活動的最大時間量
leakDetectionThreshold 0 0 如果大於0且不是單元測試,則進一步判斷:(leakDetectionThreshold < SECONDS.toMillis(2) or (leakDetectionThreshold > maxLifetime && maxLifetime > 0),會被重置為0 . 即如果要生效則必須>0,而且不能小於2秒,而且當maxLifetime > 0時不能大於maxLifetime 記錄訊息之前連線可能離開池的時間量,表示可能的連線洩漏
dataSource null null 這個屬性允許你直接設定資料來源的例項被池包裝,而不是讓HikariCP通過反射來構造它
schema null driver default 該屬性為支援模式概念的資料庫設定預設模式
threadFactory null null 此屬性允許您設定將用於建立池使用的所有執行緒的java.util.concurrent.ThreadFactory的例項。
scheduledExecutor null null 此屬性允許您設定將用於各種內部計劃任務的java.util.concurrent.ScheduledExecutorService例項

§資料來源配置案例

  資料庫連線池properties檔案配置資訊:

###資料來源配置###

#預設就是hikari,可預設
#spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mysql?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=root

#預設30000ms,即30s
#spring.datasource.hikari.connection-timeout=30000
#存活時間,預設600000ms,即10min
spring.datasource.hikari.idle-timeout=600000
#連線池的最大尺寸(閒置連線+正在使用的連線),預設10
spring.datasource.hikari.maximum-pool-size=200
#最小空閒連線數,預設10
spring.datasource.hikari.minimum-idle=50
spring.datasource.hikari.pool-name=私有連線池

  HikariDataSource在應用啟動後,第一次資料庫互動的時候載入連線池資訊,這就是因為Spring Boot 2.x連線資料庫用到了懶載入。

§Reference