連線池是一種建立和管理連線池的技術,這些連線池已準備好供任何需要它們的執行緒使用。連線池可以大幅提高 Java 應用程式的效能,同時減少整體資源使用量。
連線池的運作方式
大多數應用程式只需要在主動處理交易時,讓執行緒有權存取 JDBC 連線,這通常只需要幾毫秒即可完成。當不處理交易時,連線會處於閒置狀態。連線池允許其他執行緒使用閒置連線來執行有用的工作。
實際上,當執行緒需要針對 MySQL 或其他具有 JDBC 的資料庫執行工作時,它會從池中請求連線。當執行緒完成使用連線時,它會將其返回到池中,以便其他執行緒可以使用它。
當連線從池中借出時,它會由請求它的執行緒專屬使用。從程式設計的角度來看,這與您的執行緒每次需要 JDBC 連線時呼叫 DriverManager.getConnection()
相同。透過連線池,您的執行緒最終可能會使用新的連線或已存在的連線。
連線池的優點
連線池的主要優點是
-
減少連線建立時間。
雖然這通常不是 MySQL 與其他資料庫相比所提供的快速連線設定的問題,但建立新的 JDBC 連線仍然會產生網路和 JDBC 驅動程式的額外負荷,如果回收連線,則可以避免這種額外負荷。
-
簡化的程式設計模型。
當使用連線池時,每個執行緒都可以像它已建立自己的 JDBC 連線一樣運作,讓您可以使用直接的 JDBC 程式設計技術。
-
受控制的資源使用量。
如果您每次在執行緒需要連線時都建立新的連線,而不是使用連線池,則應用程式的資源使用量可能會浪費,而且當應用程式在高負載下時,可能會導致應用程式出現無法預測的行為。
使用 Connector/J 的連線池
JDBC 中的連線池概念已透過 JDBC 2.0 選用介面進行標準化,而且所有主要的應用程式伺服器都有與 MySQL Connector/J 搭配使用的這些 API 的實作。
一般而言,您可以在應用程式伺服器組態檔案中設定連線池,並透過 Java 命名和目錄介面 (JNDI) 存取它。下列程式碼顯示您如何使用 J2EE 應用程式伺服器中部署的應用程式中的連線池
範例 8.1 Connector/J:將連線池與 J2EE 應用程式伺服器搭配使用
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.InitialContext;
import javax.sql.DataSource;
public class MyServletJspOrEjb {
public void doSomething() throws Exception {
/*
* Create a JNDI Initial context to be able to
* lookup the DataSource
*
* In production-level code, this should be cached as
* an instance or static variable, as it can
* be quite expensive to create a JNDI context.
*
* Note: This code only works when you are using servlets
* or EJBs in a J2EE application server. If you are
* using connection pooling in standalone Java code, you
* will have to create/configure datasources using whatever
* mechanisms your particular connection pooling library
* provides.
*/
InitialContext ctx = new InitialContext();
/*
* Lookup the DataSource, which will be backed by a pool
* that the application server provides. DataSource instances
* are also a good candidate for caching as an instance
* variable, as JNDI lookups can be expensive as well.
*/
DataSource ds =
(DataSource)ctx.lookup("java:comp/env/jdbc/MySQLDB");
/*
* The following code is what would actually be in your
* Servlet, JSP or EJB 'service' method...where you need
* to work with a JDBC connection.
*/
Connection conn = null;
Statement stmt = null;
try {
conn = ds.getConnection();
/*
* Now, use normal JDBC programming to work with
* MySQL, making sure to close each resource when you're
* finished with it, which permits the connection pool
* resources to be recovered as quickly as possible
*/
stmt = conn.createStatement();
stmt.execute("SOME SQL QUERY");
stmt.close();
stmt = null;
conn.close();
conn = null;
} finally {
/*
* close any jdbc instances here that weren't
* explicitly closed during normal code path, so
* that we don't 'leak' resources...
*/
if (stmt != null) {
try {
stmt.close();
} catch (sqlexception sqlex) {
// ignore, as we can't do anything about it here
}
stmt = null;
}
if (conn != null) {
try {
conn.close();
} catch (sqlexception sqlex) {
// ignore, as we can't do anything about it here
}
conn = null;
}
}
}
}
如上述範例所示,在取得 JNDI InitialContext
,並查閱 DataSource
之後,其餘程式碼會遵循熟悉的 JDBC 慣例。
當使用連線池時,請務必確保關閉連線以及它們所建立的任何項目 (例如陳述式或結果集)。無論您的程式碼中發生什麼情況 (例外、控制流程等等),此規則都適用。當這些物件關閉時,可以重複使用它們;否則,它們會處於閒置狀態,這表示它們代表的 MySQL 伺服器資源 (例如緩衝區、鎖定或套接字) 會繫結一段時間,或者在最糟的情況下可能會永遠繫結。
調整連線池的大小
每個與 MySQL 的連線都會在用戶端和伺服器端產生額外負荷 (記憶體、CPU、內容切換等等)。每個連線都會限制可供您的應用程式以及 MySQL 伺服器使用的資源數量。無論連線是否正在執行任何有用的工作,都會使用許多這些資源!可以調整連線池以最大化效能,同時將資源利用率保持在應用程式開始失敗而不是僅僅執行較慢的點之下。
連線池的最佳大小取決於預期的負載和平均資料庫交易時間。實際上,最佳的連線池大小可能比您預期的要小。例如,如果您採用 Oracle 的 Java Petstore 藍圖應用程式,則使用 MySQL 和 Tomcat 的 15-20 個連線的連線池可以使用可接受的回應時間為相對適中的負載 (600 個並行使用者) 提供服務。
若要正確調整應用程式的連線池大小,請使用 Apache JMeter 或 The Grinder 等工具建立負載測試指令碼,並負載測試您的應用程式。
判斷起點的簡單方法是將連線池的最大連線數設定為無界限,執行負載測試,並測量同時使用的最大連線數。然後,您可以從那裡反向工作,以判斷哪些最小和最大集區連線值可為您的特定應用程式提供最佳效能。
驗證連線
MySQL Connector/J 可以透過針對伺服器執行輕量 ping 來驗證連線。在負載平衡連線的情況下,會針對保留的所有作用中集區內部連線執行此作業。這對於使用連線池的 Java 應用程式很有幫助,因為池可以使用此功能來驗證連線。根據您的連線池和組態,可以在不同的時間執行此驗證
在池將連線傳回應用程式之前。
當應用程式將連線傳回池時。
在定期檢查閒置連線期間。
若要使用此功能,請在您的連線池中指定以 /* ping */
開頭的驗證查詢。請注意,語法必須完全按照指定。這會導致驅動程式將 ping 傳送到伺服器,並傳回虛擬的輕量結果集。當使用 ReplicationConnection
或 LoadBalancedConnection
時,ping 會透過所有作用中連線傳送。
正確指定語法至關重要。語法必須完全相同,以提高效率,因為此測試會針對執行的每個陳述式完成
protected static final String PING_MARKER = "/* ping */";
...
if (sql.charAt(0) == '/') {
if (sql.startsWith(PING_MARKER)) {
doPingInstead();
...
以下任何程式碼片段都無法運作,因為 ping 語法對空白、大小寫和位置很敏感
sql = "/* PING */ SELECT 1";
sql = "SELECT 1 /* ping*/";
sql = "/*ping*/ SELECT 1";
sql = " /* ping */ SELECT 1";
sql = "/*to ping or not to ping*/ SELECT 1";
先前所有的陳述都會發出正常的 SELECT
陳述式,且不會被轉換為輕量級的 ping。此外,對於負載平衡連線,該陳述式將針對內部集區中的一個連線執行,而不是驗證每個底層的實體連線。這會導致非活動的實體連線處於過時狀態,並可能失效。如果 Connector/J 隨後重新平衡,它可能會選取已失效的連線,導致將例外狀況傳遞至應用程式。為了協助防止這種情況發生,您可以使用 loadBalanceValidateConnectionOnSwapServer
在使用前驗證連線。
如果您的 Connector/J 部署使用允許您指定驗證查詢的連線集區,請善加利用它,但請確保查詢完全以 /* ping */
開頭。如果您使用 Connector/J 的負載平衡或具備複寫感知的功能,這點尤其重要,因為它將有助於保持連線的運作,否則連線將會過時並失效,進而導致後續問題。