作者:黃炎帝
前言
我們能否繞開 http 協議,直接測試資料庫的效能?是否覺得從資料庫中匯出 CSV 檔案來構造壓測資料很麻煩?怎樣在壓測結束後做資料清理?能不能通過資料庫中的插入(刪除)記錄對壓測請求做斷言?使用阿里雲效能測試工具 PTS 可以輕鬆解決上述問題。
什麼是 JDBC
JDBC(Java DataBase Connectivity,Java 資料庫連線)是一種用於執行 SQL 語句的 Java API,可以為多種關聯式資料庫提供統一訪問,它由一組用 Java 語言編寫的類和介面組成。JDBC 提供了一種基準,據此可以構建更高階的工具和介面,使資料庫開發人員能夠編寫資料庫應用程式。
簡單地說,JDBC 可做三件事:與資料庫建立連線、傳送運算元據庫的語句並處理結果。
JDBC 的設計原理
整體架構
JDBC 制定了一套和資料庫進行互動的標準,資料庫廠商提供這套標準的實現,這樣就可以通過統一的 JDBC 介面來連線各種不同的資料庫。可以說 JDBC 的作用是遮蔽了底層資料庫的差異,使得使用者按照 JDBC 寫的程式碼可以在各種不同的資料庫上進行執行。那麼這是如何實現的呢?如下圖所示:
JDBC 定義了 Driver 介面,這個介面就是資料庫的驅動程式, 所有跟資料庫打交道的操作最後都會歸結到這裡 ,資料庫廠商必須實現該介面,通過這個介面來完成上層應用的呼叫者和底層具體的資料庫進行互動。Driver 是通過 JDBC 提供的 DriverManager 進行註冊的,註冊的程式碼寫在了 Driver 的靜態塊中,如 MySQL 的註冊程式碼如下所示:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
作為驅動定義的規範 Driver,它的主要目的就是和資料庫建立連線,所以其介面也很簡單,如下所示:
public interface Driver {
//建立連線
Connection connect(String url, java.util.Properties info)
throws SQLException;
boolean acceptsURL(String url) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
throws SQLException;
int getMajorVersion();
int getMinorVersion();
boolean jdbcCompliant();
public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}
作為 Driver 的管理者 DriverManager,它不僅負責 Driver 的註冊/登出,還可以直接獲取連線。它是怎麼做到的呢?觀察下面程式碼發現,實際是通過遍歷所以已經註冊的 Driver,找到一個能夠成功建立連線的 Driver,並且將 Connection 返回,DriverManager 就像代理一樣,將真正建立連線的過程還是交給了具體的 Driver。
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
Connection 設計
通過上節我們知道資料庫提供商通過實現Driver介面來向使用者提供服務,Driver介面的核心方法就是獲取連線。Connection是和資料庫打交道的核心介面,下面我們看看它的設計方案。
通過觀察設計圖我們發現主要有兩類介面:DataSource 和 Connection。下面我們逐一進行介紹。
- DataSource
直接看原始碼,如下所示,發現它的核心方法竟然和 Driver 一樣,也是獲取連線。那為什麼還要 DataSource 呢?Driver 本身不就是獲取連線的嗎?下面我們就看看 DataSource 到底是怎麼獲取連線的。
public interface DataSource extends CommonDataSource, Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
}
然而我們發現 JDBC 只定義了 DataSource 的介面,並沒有給出具體實現,下面我們就以 Spring 實現的 SimpleDriverDataSource 為例,來看看它是怎麼做的,程式碼如下所示,發現 DataSource 的 getConnection(...)方法,最後竟然還是交由 driver.connect(...)去真正建立連線。所以又回到最開始我們所描述的, Driver 才是真正的與資料庫打交道的介面。
protected Connection getConnectionFromDriver(Properties props) throws SQLException {
Driver driver = getDriver();
String url = getUrl();
Assert.notNull(driver, "Driver must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Creating new JDBC Driver Connection to [" + url + "]");
}
return driver.connect(url, props);
}
那麼問題來了,為什麼還需要 DataSource 這樣的介面,豈不多此一舉麼?顯然不會。DataSource 是加強版的 Driver。它將核心的建立連線的過程交由 Driver 執行,而對於建立快取,處理分散式事務和連線池等看似與建立連線無關的事情自己來處理。如類的設計圖所示,以 PTS 使用的 Druid 連線池為例:
- ConnectionPoolDataSource:連線池的實現,此資料來源實現並不直接建立資料庫物理連線,而是一個邏輯實現,它的作用在於池化資料庫物理連線。
- PooledConnection:配合 ConnectionPoolDataSource,由它獲取一個池化物件 PooledConnection,再通過該 PooledConnection 間接獲取到物理連線。
顯然,通過連線池我們可以從連線的管理中抽身,提高連線的利用效率,也能提升壓力機的施壓能力。
Statement 設計
建立連線之後,使用者可能要開始寫 SQL 語句,並且交由資料庫去執行了。這些是通過 Statement 來實現的。主要分為:
- Statement:定義一個靜態的 SQL 語句,資料庫每次執行都需要重新編譯,一般用於僅執行一次查詢並返回結果的情形。
- PreparedStatement:定義一個帶參的預編譯的 SQL 語句,下次執行時,會從快取中取出遍以後的語句,而不需要重新編譯一遍,適用於執行多次相同邏輯的 SQL 語句,當然它還有防 SQL 注入等功能,安全性和效率較高,使用比較頻繁。對於效能測試來說,選擇 PreparedStatement 最為合適。
- CallableStatement:用來呼叫儲存過程。
ResultSet 設計
JDBC 使用 ResultSet 介面來承接 Statement 的執行結果。ResultSet 使用指標的方式(next())來逐條獲取檢索結果,當指標指向某條資料時,使用者可以自由的選擇獲取某一列的資料。PTS 通過將 ResultSet 轉化成 CSV 檔案,輔助使用者以一條 SQL 語句,構造複雜的壓測資料。
JDBC 架構總結
通過上面的介紹我們發現,JDBC 的設計還是層次感分明的。
(1)Driver 和 DriverManager 是面向資料庫的,設計了一套 Java 訪問資料的規範,資料庫廠商只需要實現這套規範即可;
(2)DataSource 和 Connection 是面向應用程式開發者的,它們不關心 JDBC 具體是如何跟資料庫進行互動的,通過統一的 DataSource 介面就可以拿到 Connection,使用者的資料操作都可以通過這個 Connection 來實現了;
(3)Statement 承載了具體的 SQL 命令,使用者可以定義不同的 Statement 來向資料庫傳送指令;
(4)ResultSet 是用來承載 SQL 命令的執行結果。
至此,完成了 載入驅動 -> 建立連線 -> 執行命令 -> 返回結果 這樣的和資料庫互動的整個過程。如果把這個過程靈活的嵌入到 PTS 效能測試中,便可以解決前言提到的各種問題。
JDBC 在效能測試中的應用
資料庫效能測試
- 背景
大多數對資料庫的操作都是通過 HTTP、FTP 或其他協議執行的,但是在某些情況下,繞開中間協議直接測試資料庫也很有意義。例如我們希望不觸發所有相關查詢,而只測試特定 high-value 查詢的效能;驗證新資料庫在高負載下的效能。2.驗證某些資料庫連線池引數,例如最大連線數 3.節省時間和資源。當我們想要優化 SQL 時,修改程式碼中的 SQL 語句和其他資料庫操作非常繁瑣,通過 JDBC 壓測,我們可以避免侵入程式碼,集中精力在 SQL 調優上。
- 步驟
1、建立場景。我們在 PTS 控制檯的【壓測中心】->【建立場景】中建立 PTS 壓測場景;
2、場景配置。PTS 支援對 MySQL、PostgreSQL 等四種資料庫發起壓測。使用者填寫 JDBC URL、使用者名稱、密碼和 SQL 即可發起壓測。同時,PTS 還支援提取 ResultSet 中的資料作為出參,給下游 API 使用;對響應進行斷言。
3、壓測中監控和壓測報告。PTS 支援繫結阿里雲 RDS 雲資源監控,在壓測過程中觀察 RDS 實時效能指標。此外,PTS 還提供清晰完備的壓測報告以及取樣日誌,供使用者隨時檢視。
壓測資料構造
- 背景
在模擬不同使用者登入、壓測業務引數傳遞等場景中,需要使用引數功能來實現壓測的請求中各種動態操作。如果使用傳統的 CSV 檔案引數,會受到檔案大小的限制,且手動建立耗費精力。使用 JDBC 來構造壓測資料,可以避免以上問題。
- 步驟
1、新增資料來源。在場景編輯-資料來源管理中,選擇新增 DB 資料來源,輸入 URL、使用者名稱、密碼和 SQL。
2、新增引數。填寫自定義引數名和列索引。
3、除錯驗證。點選除錯場景,即可驗證提取的結果集是否符合預期。接著,我們就可以在任意想要使用引數的
地方使用${}引用即可。
壓測髒資料清理
- 背景
針對寫請求的壓測,會在資料庫中生成大量髒資料。如何在壓測結束後自動清理?
- 步驟
PTS 給使用者提供瞭解決方案。PTS 支援對串聯鏈路作邏輯上的順序編排,即前置鏈路、普通鏈路和後置鏈路。執行順序由先到後。設定某條串聯鏈路為後置鏈路,填寫迴圈次數即可。
更多交流,歡迎進釘釘群溝通,PTS 使用者交流釘釘群號:11774967。
此外,PTS 近期對售賣方式做了全新升級,基礎版價格直降 50%!5W 併發價格只需 199,免去自運維壓測平臺煩惱!更有新使用者 0.99 體驗版、VPC 壓測專屬版,歡迎大家選購!