连接池是一种创建和管理连接池的技术,这些连接池可以随时被任何需要它们的 线程 使用。连接池可以极大地提高 Java 应用程序的性能,同时减少整体资源使用。
连接池的工作原理
大多数应用程序只需要一个线程在积极处理 事务 时才能访问 JDBC 连接,而这通常只需要几毫秒就能完成。在不处理事务时,连接处于闲置状态。连接池使闲置连接可以被其他线程使用来完成有用的工作。
在实践中,当一个线程需要对 MySQL 或其他使用 JDBC 的数据库执行操作时,它会从池中请求一个连接。当线程完成使用连接后,它会将连接返回到池中,以便其他线程可以使用它。
当连接从池中借用时,它将被请求它的线程独占使用。从编程角度来看,这与您的线程每次需要 JDBC 连接时都调用 DriverManager.getConnection()
相同。使用连接池时,您的线程最终可能使用新的连接或已有的连接。
连接池的优势
连接池的主要优势是
-
减少连接创建时间。
虽然这通常不是问题,因为与其他数据库相比,MySQL 的快速连接设置,但创建新的 JDBC 连接仍然会产生网络和 JDBC 驱动程序开销,如果循环使用连接,这些开销将被避免。
-
简化的编程模型。
使用连接池时,每个线程都可以像创建了自己的 JDBC 连接一样工作,允许您使用简单的 JDBC 编程技术。
-
受控的资源使用。
如果您每次线程需要连接时都创建一个新的连接,而不是使用连接池,那么您的应用程序的资源使用将是浪费的,并且当应用程序处于高负载状态时,可能会导致应用程序出现不可预测的行为。
使用 Connector/J 进行连接池
JDBC 中的连接池的概念已通过 JDBC 2.0 可选接口进行了标准化,并且所有主要应用程序服务器都有这些 API 的实现,这些实现可以与 MySQL Connector/J 协同工作。
通常,您在应用程序服务器的配置文件中配置连接池,并通过 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 蓝图应用程序为例,一个拥有 15-20 个连接的连接池可以为使用 MySQL 和 Tomcat 的中等负载(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 的负载均衡或复制感知功能,这一点尤其重要,因为它将有助于使其他方式会过时并死亡的连接保持活动状态,从而避免以后出现问题。