Глава 8 Пулы соединений с Connector/J
Пул соединений — это техника создания и управления пулом соединений, готовых к использованию любым приложением, которое их запросит. Пул соединений может значительно повысить производительность вашего Java-приложения, одновременно уменьшая общее использование ресурсов.
Как работают пулы соединений
Большинству приложений требуется, чтобы нить потока имела доступ к JDBC-соединению только при активной обработке транзакции, что часто занимает всего несколько миллисекунд. Когда транзакция не обрабатывается, соединение находится в режиме ожидания. Пулы соединений позволяют использовать неактивное соединение другой нитью для выполнения полезной работы.
На практике, когда нить потока должна выполнить работу с базой данных MySQL или другой базой данных с помощью JDBC, она запрашивает соединение из пула. Когда нить закончила работу с соединением, она возвращает его в пул, чтобы оно могло быть использовано другими нитями.
Когда соединение выдается из пула, оно используется исключительно нитью, которая его запросила. С точки зрения программирования, это то же самое, как если бы ваша нить потока вызывала DriverManager.getConnection() каждый раз, когда ей нужно JDBC-соединение. С использованием пула соединений ваша нить потока может использовать либо новое соединение, либо уже существующее.
Преимущества использования пулов соединений
Основные преимущества использования пулов соединений:
-
Уменьшение времени создания соединений.
Хотя это обычно не проблема с быстрой настройкой соединений, которые предоставляет MySQL по сравнению с другими базами данных, создание новых JDBC-соединений все же влечет за собой накладные расходы на сетевой и JDBC-драйвер, которых можно избежать при повторном использовании соединений.
-
Упрощенная модель программирования.
При использовании пулов соединений каждая отдельная нить может действовать так, как будто она создала свое собственное JDBC-соединение, что позволяет использовать простые методы программирования JDBC.
-
Управляемое использование ресурсов.
Если вы создаете новое соединение каждый раз, когда нить его требует, а не используете пул соединений, использование ресурсов вашим приложением может быть неэффективным, и это может привести к непредсказуемому поведению приложения при высокой нагрузке.
Использование пулов соединений с Connector/J
Концепция пулов соединений в JDBC стандартизирована с помощью интерфейсов JDBC 2.0 Optional, и все основные серверы приложений имеют реализации этих API, которые работают с MySQL Connector/J.
Как правило, вы настраиваете пул соединений в конфигурационных файлах сервера приложения и получаете доступ к нему через Java Naming and Directory Interface (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 имеет накладные расходы (память, ЦП, переключения контекста и т. д.) как на стороне клиента, так и на стороне сервера. Каждое соединение ограничивает количество ресурсов, доступных вашему приложению, а также серверу MySQL. Многие из этих ресурсов будут использоваться независимо от того, выполняет ли соединение полезную работу или нет!
Пулы соединений могут настраиваться для максимальной производительности при одновременном поддержании использования ресурсов ниже точки, где ваше приложение начнет сбоить, а не просто работать медленнее.
Оптимальный размер пула соединений зависит от ожидаемой нагрузки и среднего времени выполнения транзакций базы данных. На практике оптимальный размер пула соединений может быть меньше, чем вы ожидаете. Если взять, к примеру, проект Java Petstore от Oracle, то пул соединений размером 15-20 соединений может обслуживать относительно умеренную нагрузку (600 одновременных пользователей) с использованием MySQL и Tomcat с приемлемыми временами отклика.
Для правильного определения размера пула соединений для вашего приложения создайте сценарии нагрузочных тестов с помощью инструментов, таких как 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, так как это поможет сохранить активные соединения, которые в противном случае устареют и станут недействительными, что позже вызовет проблемы.
© 2025 Oracle
Licensed under the GPLv2 License.