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的程式碼初看邏輯比較複雜,這裡先從如何獲取資料庫連線開始認識它。獲取連線相關的類介面如下:
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原始碼地址
end.
相關閱讀:
HikariPool原始碼(二)設計思想借鑑
Google guava原始碼之EventBus
Java呼叫鏈跟蹤關鍵技術(四)SQL監控
Java極客站點: javageektour.com/