天天看點

穩,從資料庫連接配接池 testOnBorrow 看架構設計

作者:京東雲開發者

本文從 Commons DBCP testOnBorrow 的作用機制着手,管中窺豹,從一點去分析資料庫連接配接池擷取的過程以及架構分層設計。

以下内容會按照每層的作用,貫穿分析整個調用流程。

1️⃣架構層 commons-pool

The indication of whether objects will be validated before being borrowed from the pool.

If the object fails to validate, it will be dropped from the pool, and we will attempt to borrow another.

testOnBorrow 不是 dbcp 定義的,是 commons-pool 定義的。commons-pool 詳細的定義了資源池使用的一套規範和運作流程。

/**
 * Borrow an object from the pool. get object from 資源池
 * @see org.apache.commons.pool2.impl.GenericObjectPool#borrowObject(long)
 */
public T borrowObject(final long borrowMaxWaitMillis) throws Exception {
	
	PooledObject<T> p = null;
	
    // if validation fails, the instance is destroyed and the next available instance is examined. 
    // This continues until either a valid instance is returned or there are no more idle instances available.
	while (p == null) {
        // If there is one or more idle instance available in the pool, 
        // then an idle instance will be selected based on the value of getLifo(), activated and returned.
		p = idleObjects.pollFirst();
		if (p != null) {
            // 設定 testOnBorrow 就會進行可用性校驗
			if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) {
				boolean validate = false;
				Throwable validationThrowable = null;
				try {
                    // 具體的校驗實作由實作類完成。
                    // see org.apache.commons.dbcp2.PoolableConnectionFactory
					validate = factory.validateObject(p);
				} catch (final Throwable t) {
					PoolUtils.checkRethrow(t);
					validationThrowable = t;
				}
				if (!validate) {
					try {
                        // 如果校驗異常,會銷毀該資源。
                        // obj is not valid and should be dropped from the pool
						destroy(p);
						destroyedByBorrowValidationCount.incrementAndGet();
					} catch (final Exception e) {
						// Ignore - validation failure is more important
					}
					p = null;
				}
			}
		}
	}

	return p.getObject();
}
           

2️⃣應用層 commons-dbcp

dbcp 是特定于管理資料庫連接配接的資源池。

PoolableConnectionFactory is a PooledObjectFactory

PoolableConnection is a PooledObject

/**
 * @see PoolableConnectionFactory#validateObject(PooledObject)
 */
@Override
public boolean validateObject(final PooledObject<PoolableConnection> p) {
	try {
		/**
		 * 檢測資源池對象的建立時間,是否超過生存時間
		 * 如果超過 maxConnLifetimeMillis, 不再委托資料庫連接配接進行校驗,直接廢棄改資源
		 * @see PoolableConnectionFactory#setMaxConnLifetimeMillis(long)
		 */
		validateLifetime(p);
		// 委托資料庫連接配接進行自我校驗
		validateConnection(p.getObject());
		return true;
	} catch (final Exception e) {
		return false;
	}
}

/**
 * 資料庫連接配接層的校驗。具體到是否已關閉、是否與 server 連接配接可用
 * @see Connection#isValid(int)
 */
public void validateConnection(final PoolableConnection conn) throws SQLException {
	if(conn.isClosed()) {
		throw new SQLException("validateConnection: connection closed");
	}
	conn.validate(_validationQuery, _validationQueryTimeout);
}
           

3️⃣基礎層 mysql-connector-java

Returns true if the connection has not been closed and is still valid.

這個是 java.sql.Connection 定義的規範。具體實作根據對應資料庫的 driver 來完成。使用某種機制用來探測連接配接是否可用。

/**
 * 調用 com.mysql.jdbc.MysqlIO, 發送ping 請求,檢測是否可用
 * 對比 H2 資料庫,是通過擷取目前事務級别來檢測連接配接是否可以。但是忽略了 timeout 配置,畢竟是 demo 資料庫 
 */
public synchronized boolean isValid(int timeout) throws SQLException {
	if (this.isClosed()) {
		return false;
	} else {
		try {
			this.pingInternal(false, timeout * 1000);
			return true;
		} catch (Throwable var5) {
			return false;
		}
	}
}
           

參考:MySQL 的連接配接時長控制 --interactive_timeout 和 wait_timeout_翔雲 123456 的部落格 - CSDN 部落格

總結

  • commons-pool 定義資源的完整聲明周期接口,包括:makeObject、activateObject、validateObject、passivateObject、destoryObject。資源池管理對象,通過實作這些接口即可實作資源控制。參考:org.apache.commons.pool2.PooledObjectFactory
  • 在校驗過程中,牽涉到很多時間,包括資源池對象的建立時間、生存時間、資料庫連接配接的逾時時間、Mysql 連接配接空閑逾時時間等。不同層為了服務可靠性,提供不同的時間配置。校驗也是層層遞進,最終委托到最底層來判斷。
  • 校驗過程中,對于連接配接也會由是否已關閉的校驗(isClosed() )。包括 PoolableConnection#isClosed, Connection#isClosed, Socket#isClosed。 同樣也是層層保障,確定整個架構的可靠。
  • 定義一套完整嚴謹的規範和标準,比實作一個具體的功能或者特性要求更高 。commons-pool 和 jdbc 定義了規範,commons-dbcp 和 mysql-connector-java 完成了具體的實作。有了規範和接口,元件和架構的對接和相容才變為可能。

more 了解高可用

在閱讀 MySQL Driver 源碼過程中,有個點要特别記錄下。以 MySQL Driver 建立連接配接為例,用重試連接配接實作可用性,這就是高可用。

高可用不是一個口号,也不是複雜的概念和公式。能夠實實在在體系化的解決一類問題就是架構的目的。結合上述的架構分層,如果解決問題的方案通用性好,并且實作很優雅,就是好的架構。

// autoReconnect 
public void createNewIO(boolean isForReconnect) throws SQLException {
    synchronized (getConnectionMutex()) {
        // jdbc.url autoReconnect 指定為 true,識别為 HighAvailability。emmm..... 
        if (!getHighAvailability()) {
            connectOneTryOnly(isForReconnect, mergedProps);
            return;
        }
        // maxReconnects 預設為 3,重試失敗的提示就是: Attempted reconnect 3 times. Giving up.
        connectWithRetries(isForReconnect, mergedProps);
    }
}
           

作者:京東物流 楊攀

來源:京東雲開發者社群

繼續閱讀