HikariPool原始碼(一)初識

Java極客發表於2020-03-29

HikariPool原始碼(一)初識

Java極客  |  作者  /  鏗然一葉
這是Java極客的第 50 篇原創文章

1、HikariPool是什麼

HikariPool是一個開源資料庫連線池管理工具,以效能優秀著稱。

2、從HikariPool中可以學到什麼

1.HikariPool用到很多併發和執行緒管理工具,可以學習它們的用法。

2.有不少提升效能的用法,可以借鑑。

3.用到了一些Java的特性,使得程式碼看起來更友好,簡潔,例如:
1)int的定義

// 以下這個定義是合法的,可用_來表示千分位,方便識別具體值是多少,而不用一個個的數
int i = 10_000;
複製程式碼

2)通過Lambda來簡化內部類的定義,例如HikariPool.java中的如下程式碼:

         closeConnectionExecutor.execute(() -> {
            quietlyCloseConnection(connection, closureReason);
            if (poolState == POOL_NORMAL) {
               fillPool();
            }
         });
複製程式碼

不用Lambda表示式則為如下寫法:

         closeConnectionExecutor.execute(new Runnable() {
            @Override
            public void run() {
               quietlyCloseConnection(connection, closureReason);
               if (poolState == POOL_NORMAL) {
                  fillPool();
               }
            }
         });
複製程式碼

因為Runnable只有一個方法,因此可通過

() -> {
複製程式碼

替換如下程式碼,使得程式碼更加簡潔:

new Runnable() {
            @Override
            public void run() {
            }
複製程式碼

3、初識HikariPool

HikariPool的程式碼初看邏輯比較複雜,這裡先從如何獲取資料庫連線開始認識它。獲取連線相關的類介面如下:

HikariPool原始碼(一)初識

3.1、HikariPool

HikariPool是連線池管理類,負責管理資料庫連線。

3.2、ConcurrentBag

ConcurrentBag是一個封裝的併發管理工具,負責管理池化資源,不僅僅是資料庫連線,其他池化資源都可以通過它來管理。

3.2.1、類定義

// 通過泛型要求其包含的池化資源必須實現IConcurrentBagEntry介面
public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseable
複製程式碼

3.2.2、池化資源介面

   // 池化資源介面
   public interface IConcurrentBagEntry
   {
      // 池化資源的狀態定義
      int STATE_NOT_IN_USE = 0;
      int STATE_IN_USE = 1;
      int STATE_REMOVED = -1;
      int STATE_RESERVED = -2;

      // 通過CAS操作而非鎖來提高併發效率
      boolean compareAndSet(int expectState, int newState);
      void setState(int newState);
      int getState();
   }
複製程式碼

3.2.3、獲取PoolEntry

public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
複製程式碼

ConcurrentBag程式碼比較多,後面將做更詳細的介紹。

3.3、PoolEntry

PoolEntry是池化資源的入口類,實現了IConcurrentBagEntry介面,同時一對一持有Connection,方便對Connection管理。其建立連線代理的程式碼如下:

   Connection createProxyConnection(final ProxyLeakTask leakTask, final long now)
   {
      return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
   }
複製程式碼

3.4、ProxyFactory

ProxyFactory用於獲取資料庫連線。

3.4.1、得到連線

   static ProxyConnection getProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit)
   {
      // Body is replaced (injected) by JavassistProxyFactory
      throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
   }
複製程式碼

1.這個方法內部丟擲了異常,實際方法體會被JavassistProxyFactory替換。

2.這裡返回ProxyConnection而不是Connection會更靈活,方便實現資料庫監控。 關於資料庫監控參考:Java呼叫鏈跟蹤關鍵技術(四)SQL監控

3.4.2、JavassistProxyFactory.java

用於替換ProxyFactory類的方法體。

   private static void modifyProxyFactory() throws NotFoundException, CannotCompileException, IOException {
      System.out.println("Generating method bodies for com.zaxxer.hikari.proxy.ProxyFactory");

      String packageName = ProxyConnection.class.getPackage().getName();
      CtClass proxyCt = classPool.getCtClass("com.zaxxer.hikari.pool.ProxyFactory");
      for (CtMethod method : proxyCt.getMethods()) {
         switch (method.getName()) {
           // 這裡對getProxyConnection方法體內容做了替換
            case "getProxyConnection":
               method.setBody("{return new " + packageName + ".HikariProxyConnection($$);}");
               break;
            case "getProxyStatement":
               method.setBody("{return new " + packageName + ".HikariProxyStatement($$);}");
               break;
            case "getProxyPreparedStatement":
               method.setBody("{return new " + packageName + ".HikariProxyPreparedStatement($$);}");
               break;
            case "getProxyCallableStatement":
               method.setBody("{return new " + packageName + ".HikariProxyCallableStatement($$);}");
               break;
            case "getProxyResultSet":
               method.setBody("{return new " + packageName + ".HikariProxyResultSet($$);}");
               break;
            case "getProxyDatabaseMetaData":
               method.setBody("{return new " + packageName + ".HikariProxyDatabaseMetaData($$);}");
               break;
            default:
               // unhandled method
               break;
         }
      }
      // 替換後的class直接放到classes目錄下    
      proxyCt.writeFile(genDirectory + "target/classes");
   }
複製程式碼

3.5、ProxyConnection

ProxyConnection是個連線代理,持有Connection。

3.6、ProxyLeakTask

ProxyLeakTask用於監控資料庫連線是否洩露,監控思路是:

1.在建立連線時通過ScheduledExecutorService延遲執行ProxyLeakTask,這個延遲執行的時間為配置的leakDetectionThreshold,如果從建立連線代理開始到超過leakDetectionThreshold還沒有關閉連線代理,ProxyLeakTask就會被執行,一旦執行就說明連線代理可能洩露了。

2.如果在leakDetectionThreshold時間內連線代理被關閉,則ProxyLeakTask會被cancel,這樣就不會被執行。

class ProxyLeakTask implements Runnable
{
   private static final Logger LOGGER = LoggerFactory.getLogger(ProxyLeakTask.class);
   static final ProxyLeakTask NO_LEAK;

   // 用於延遲排程ProxyLeakTask
   private ScheduledFuture<?> scheduledFuture;
   private String connectionName;
   private Exception exception;
   private String threadName;
   private boolean isLeaked;

   static
   {
      // 不需要監控連線洩露的ProxyLeakTask的實現類
      NO_LEAK = new ProxyLeakTask() {
         @Override
         void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) {}

         @Override
         public void run() {}  // 預設啥都不做

         @Override
         public void cancel() {} // 預設啥都不做
      };
   }

   ProxyLeakTask(final PoolEntry poolEntry)
   {
      this.exception = new Exception("Apparent connection leak detected");
      this.threadName = Thread.currentThread().getName();
      this.connectionName = poolEntry.connection.toString();
   }

   private ProxyLeakTask()
   {
   }

   void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold)
   {
      // 通過超過leakDetectionThreshold時間後,延遲呼叫ProxyLeakTask,來報告洩漏資訊
      scheduledFuture = executorService.schedule(this, leakDetectionThreshold, TimeUnit.MILLISECONDS);
   }

   /** {@inheritDoc} */
   @Override
   // 一旦被執行,說明獲取連線到關閉超過了leakDetectionThreshold時間
   public void run()
   {
      isLeaked = true;

      final StackTraceElement[] stackTrace = exception.getStackTrace();
      final StackTraceElement[] trace = new StackTraceElement[stackTrace.length - 5];
      System.arraycopy(stackTrace, 5, trace, 0, trace.length);

      exception.setStackTrace(trace);
      // 下面是監控到連線洩漏的處理,這裡只是記錄到日誌中,如果通過一個介面處理,並可以讓使用者動態實現會更靈活
      LOGGER.warn("Connection leak detection triggered for {} on thread {}, stack trace follows", connectionName, threadName, exception);
   }

   void cancel()
   {
      scheduledFuture.cancel(false);
      if (isLeaked) {  // 檢查到洩漏後連線被關閉,則給一個提示資訊
         LOGGER.info("Previously reported leaked connection {} on thread {} was returned to the pool (unleaked)", connectionName, threadName);
      }
   }
}
複製程式碼

3.7、ProxyLeakTaskFactory

ProxyLeakTaskFactory是個工廠類,用於建立ProxyLeakTask,之所以有這個工廠類,是因為有不同的ProxyLeakTask實現。可以根據配置來決定是否要監控連線洩漏。

   ProxyLeakTask schedule(final PoolEntry poolEntry)
   {
      // 根據配置來建立不同的代理洩露監控類
      return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry);
   }
複製程式碼

以上,是對HikariPool的初步認識,後面將進一步做更詳細的介紹。

4、HiKaripool原始碼地址

1.碼雲映象
2.github倉庫

end.


相關閱讀:
HikariPool原始碼(二)設計思想借鑑
Google guava原始碼之EventBus
Java呼叫鏈跟蹤關鍵技術(四)SQL監控


Java極客站點: javageektour.com/

相關文章