Что такое connection pool
Database Connection Pool
Добрый день, хабралюди!
2 недели назад я начал работать juior java разработчиком, и, соответственно, получать много нового для себя опыта. Сегодня я решил совместить приятное с полезным и начать этот опыт оформлять в письменные мысли — в виде статей о тех технологиях, принципах и приёмах, с которыми я столкнулся на своём джуниорском пути. Нижеследующая статья — первая среди подобных, и выкладывая её здесь, я хочу, во-первых, понять, нужны ли хабрасообществу подобные вещи — рассказы не умудрённых опытом и сотнями проектов старожилов, а небольшие попытки поделится опытом от джуниора джуниору, — а во-вторых, как обычно, услышать замечания, исправления и критику.
Спасибо за внимание.
Подавляющее большинство современных веб-приложений использует базы данных для хранения информации. Приложение может обмениваться информацией с БД, используя соединение (database connection). Если создавать при каждом обращении к БД, получается проигрыш во времени: выполнение транзакции может занять несколько милисекунд, в то время как на создание соединения может уйти до нескольких секунд. С другой стороны, можно создать одно-единственное соединение (например, используя шаблон «Singleton») и обращаться к базе данных только через него. Но это решение чревато проблемами, в случае высокой нагрузки: если одновременно сто пользователей попытается получить доступ к базе данных используя одно соединение, образуется очередь, что также пагубно сказывается на производительности приложения.
Database Connection Pool (dbcp) — это способ решения изложенной выше проблемы. Он подразумевает, что в нашем распоряжении имеется некоторый набор («пул») соединений к базе данных. Когда новый пользователь запрашивает доступ к БД, ему выдаётся уже открытое соединение из этого пула. Если все открытые соединения уже заняты, создаётся новое. Как только пользователь освобождает одно из уже существующих соединений, оно становится доступно для других пользователей. Если соединение долго не используется, оно закрывается.
Пример реализации простейшего пула соединений можно найти на официальном сайте java.sun.com: Connection Pooling
Поскольку подобный подход наиболее полезен в случае enterprise- и web-приложений, вполне логично, что такой популярный контейнер сервлетов, как Apache Tomcat предоставляет собственное решение для создания dbcp. Решение это основанно на библиотеке apache-commons-dbcp. Чтобы реализовать поддержку пула соединений с своём приложении, нужно пройти через несколько этапов.
Во-первых, нужно объявить новый ресурс в контексте приложения. Ресурс (в нашем случае — БД) описывается следующим кодом:
Думаю, пояснения не нужны.
Контекст приложения описывается XML-файлом. Я считаю правильным хранить его в %document_root%/META-INF/context.xml, однако это не единственный вариант. Подробней про контекст можно почитать на сайте Tomcat’a: The Context Container.
Теперь нужно добавить ссылку на этот ресурс в web.xml:
DB Connection
jdbc/appname
javax.sql.DataSource
Container
Теперь мы можем использовать этот ресурс в нашем приложении. Для того чтобы получить объект Connection для выполнения sql-кода, исопльзуется следующий код:
InitialContext initContext= new InitialContext();
DataSource ds = (DataSource) initContext.lookup(«java:comp/env/jdbc/dbconnect»);
Connection conn = ds.getConnection();
Для получения источника данных (data source) используется механизм JNDI (подробнее про него можно почитать здесь)
Объединение подключений в пул в SQL Server (ADO.NET)
Соединение с сервером базы данных обычно состоит из нескольких длительных шагов. Необходимо установить физический канал, например сокет или именованный канал, выполнить первоначальное подтверждение установления связи с сервером, выполнить синтаксический анализ данных строки соединения, сервер должен проверить подлинность соединения, а также запустить проверку прикреплений в текущей транзакции и т. д.
На практике большинство приложений использует только одно или несколько различных конфигураций соединений. Это означает, что во время выполнения приложения многие идентичные соединения будут повторно открываться и закрываться. для снижения затрат на открытие подключений ADO.NET использует метод оптимизации, называемый пулами соединений.
Организация пулов соединений может существенно улучшить производительность и масштабируемость приложения. По умолчанию в ADO.NET включена поддержка пулов соединений. Пока организатор пулов не будет явно отключен, он оптимизирует соединения по мере их открытия и закрытия в приложении. Также можно указать несколько модификаторов строки соединения для управления поведением пула соединений. Дополнительные сведения см. в подразделе «Управление пулами соединений с помощью ключевых слов строки соединения» далее в этом разделе.
Создание и назначение пулов
При первом открытии соединения создается пул соединений, основанный на алгоритме точного совпадения, связанного с пулом строкой соединения. Каждый пул соединений связывается с отдельной строкой соединения. При открытии нового соединения, если строка соединения не соответствует в точности существующему пулу, создается новый пул. Соединения заносятся в пул отдельно для процесса, домена приложения, строки соединения и, если используется встроенная безопасность, для удостоверения Windows. Строки соединения должны точно совпадать. Ключевые слова, указанные в различном порядке для одного соединения, будут включены в пул по отдельности.
Если аргумент MinPoolSize не указан в строке соединения или указано значение 0, соединения в пуле будут закрыты после периода отсутствия активности. Однако, если значение аргумента MinPoolSize больше 0, пул соединений не уничтожается, пока не будет выгружен домен приложения AppDomain и не завершится процесс. Обслуживание неактивных или пустых пулов требует минимальных системных издержек.
Пул автоматически очищается при возникновении неустранимой ошибки, например переходе на другой ресурс.
Добавление соединений
При запросе объекта SqlConnection он получается из пула при наличии готового к использованию соединения. Чтобы соединение можно было использовать, оно не должно использоваться, иметь совпадающий контекст транзакции либо не иметь связи с каким-либо контекстом транзакций и иметь допустимую ссылку на сервер.
Дополнительные сведения о событиях, связанных с открытием и закрытием подключений, вы найдете в разделах документации по SQL Server: Audit Login, класс событий и Audit Logout, класс событий.
Удаление соединений
Организатор пулов соединений удаляет соединение из пула после его простоя в течение примерно 4-8 минут или при определении им разрыва соединения с сервером. Обратите внимание, что разорванное соединение можно определить только после попытки связи с сервером. При обнаружении соединения, которое больше не имеет связи с сервером, оно помечается как недействительное. Недействительные соединения удаляются из пула соединений только после их закрытия или возврата.
Если существующее соединение с сервером исчезло, оно может быть удалено из пула, даже если организатор пулов соединений не определил разорванное соединение и не пометил его как недопустимое. Это происходит вследствие того, что проверка соединения на допустимость уничтожит преимущества существования организатора пулов, став причиной вызова очередного цикла приема-передачи с сервером. В этом случае первая попытка использовать соединение определит его разрыв и вызовет исключение.
Очистка пула
Поддержка транзакций
При закрытии соединения оно освобождается обратно в пул и в соответствующий подраздел в зависимости от его контекста транзакции. Поэтому можно закрыть соединение без создания ошибки, даже если распределенная транзакция все еще находится в ожидании. Это позволит зафиксировать или отменить распределенную транзакцию позже.
Управление пулами соединений с помощью ключевых слов строки соединения
Свойство ConnectionString объекта SqlConnection поддерживает пары «ключ-значение» из строки соединения, с помощью которых можно изменять логику организации пулов соединений. Для получения дополнительной информации см. ConnectionString.
Фрагментация пула
Фрагментация пула является распространенной проблемой для многих веб-приложений, в которых может создаваться большое количество пулов, которые не освобождаются, пока существует процесс. Это оставляет большое количество соединений открытыми и потребляющими память, что приводит к ухудшению производительности.
Фрагментация пула из-за встроенной безопасности
Соединения заносятся в пул в соответствии со строкой соединения, а также удостоверением пользователя. Таким образом, при использовании на веб-узле обычной проверки подлинности или проверки подлинности Windows, а также встроенной безопасности имени входа получается один пул на пользователя. Несмотря на то, что это улучшает производительность последующих запросов пользователя к базе данных, для него недоступны преимущества соединений, установленных другими пользователями. Это также приводит по крайней мере к одному соединению с сервером базы данных для каждого пользователя. Это является побочным эффектом данной архитектуры веб-приложений, который разработчики должны соизмерить с требованиями безопасности и аудита.
Фрагментация пула из-за многочисленных баз данных
Многие поставщики услуг Интернет размещают несколько веб-узлов на одиночном сервере. Они могут использовать одну базу данных для подтверждения проверки подлинности имени входа с помощью форм и затем открывать соединение с определенной базой данных для этого пользователя или группы пользователей. Соединение с базой данных проверки подлинности заносится в пул и используется всеми. Однако для каждой базы данных существует отдельный пул соединений, увеличивающий количество соединений с сервером.
Роли приложений и пул соединений
После активации роли приложения SQL Server с помощью вызова системной хранимой процедуры sp_setapprole контекст безопасности данного соединения сбросить нельзя. Однако, если использование пула включено, соединение возвращается в пул и при повторном использовании соединения возникает ошибка. дополнительные сведения см. в статье базы знаний «SQL ошибок роли приложения с OLE DB пулом ресурсов».
Альтернативы ролям приложений
Рекомендуется пользоваться преимуществом новых механизмов безопасности, которые пришли на смену ролям приложения. Дополнительные сведения см. в разделе Создание ролей приложения в SQL Server.
Пул подключений PoolConnection
Для разработки приложения можно использовать пулы различного типа объектов, например пул потоков Thread Pool. В данной статье рассматривается только пул подключений Connection Pool к БД.
На сегодняшний день существуют готовые решения. Самое главное научиться их правильно настраивать.
Популярный контейнер сервлетов Apache Tomcat предоставляет собственное решение для создания dbcp, основанное на библиотеке apache-commons-dbcp.
apache dbcp предполагает, что имеется некоторый набор («пул») соединений к базе данных. Когда новый пользователь запрашивает доступ к БД, ему выдаётся уже открытое соединение из этого пула. Если все открытые соединения уже заняты, создаётся новое. Как только пользователь освобождает одно из уже существующих соединений, оно становится доступно для других пользователей. Если соединение долго не используется, оно закрывается.
Чтобы реализовать поддержку пула подключений нужно пройти несколько этапов.
На первом этапе необходимо объявить ресурс (базу данных) в контексте приложения. Ресурс описывается следующим кодом (пример для MySQL) :
Контекст приложения представляет файл XML. Желательно хранить его в %document_root%/META-INF/context.xml, однако это не единственный вариант. Про контекст можно почитать на официальном сайте Tomcat’a: The Context Container.
Далее следует добавить ссылку на этот ресурс в дескрипторе приложения web.xml :
После этого можно использовать пул подключений (connection pool java) в приложении. Для этого следует получить объект Connection в следующем коде :
Для получения источника данных (DataSource) используется механизм JNDI (подробнее про него можно почитать здесь)
Перед возвратом соединения в пул все Statement и ResultSet, полученные с помощью этого соединения, автоматически закрываются в соответствии с API.
Connection Pool C3P0
Пул подключений C3P0 представлен файлом c3p0-0.9.1.2.jar (версия может отличаться). Аббревиатура расшифровывается следующим образом : Connection Pool 3.0 => СP30 => C3P0.
Принцип настройки JNDI ресурсов уже представлен выше. Необходимо определить глобальные ресурсы, а в WEB приложении указать ссылку, либо можно указать непосредственно в context.xml. Все зависит от того, где хранятся библиотеки и какая конфигурация сервера.
Пример настройки C3P0 с БД MySQL :
Теперь установить пул подключений не требует особого ума. Основная задача заключается в настройке его работы, чтобы он стабильно функционировал и выдерживал нагрузку. Дополнительно о значении каждого параметры можно прочитать в документации :
Может оказаться, что jdbc Url включает символ амперсанда ‘&’. Если настройка осуществляется через XML-файл, то надо вместо param1=value1¶m2=value2 использовать param1=value1¶m2=value2.
Задача создания пула подключений к Oracle не тривиальна. Документация приведена здесь
Пул подключений на уровне java приложения, ComboPooledDataSource
Пул подключений C3P0 можно подключать не только на уровне контейнера приложений Tomcat, но и на уровне самого приложения. Для этого не требуется создавать никаких дополнительных внешних XML файлов, о которых было сказано выше.
Следующий код демонстрирует процесс создания и инициализации пула подключений :
Следующий код демонстрирует как получить Connection из пула, и как его закрыть (вернуть в пул):
Пул соединений (Connection pool)
Продолжаем писать сайт на java и сегодня мы поговорим о такой теме как пул соединение или connection pool на английском. Напомню, что это уже четвертая статья, посвященная простому java веб приложению. Остальные можно найти в рубрике Java web.
Коннект к базе данных требует затрат определенного времени. Особенно, если база данных находится удаленно. Если под каждых запрос делать подключение к базе, то отклик нашего приложения будет невероятно низким по скорости. Не говоря уже о ресурсах, которые оно потребит.
Во избежании таких проблем есть пул соединений, который открывает соединение только первый раз, а при закрытии соединения он не закрывает его, а отправляет в пул в случае если кто-то будет делать очередной запрос. По сути, приложение работает через специальный драйвер, который является оберткой для обычного jdbc драйвера и который имеет дополнительный функционал по работе с пулом.
Настройки для пула соединений программист может прописать вручную: количество активных соединений, время ожидания и т.д.
Для особенно активных, можно написать свой connection pool: класс, который буде иметь список соединений. У него будет переопределена функция close, которая будет возвращать соединение обратно в список и много других плюшек вроде таймера открытого соединения. Когда нет конекшина долгое время, соединение закрывается.
Мы не будем изобретать свой велосипед, а воспользуемся готовыми решениями. Так как мы используем Tomcat возьмем его реализацию пула соединений.
Для этого нужно в папке webapp создать папку META-INF и в ней создать xml файл context.xml.
Далее нужно вставить настройки нашего пула в этот файл:
Здесь указано по минимуму настроек. Полагаю, комментарии излишни.
Теперь осталось только использовать этот код в нашем приложении.
Создадим класс ConnectonPool и сделаем его singletone. Для тех, кто не знает: это когда нельзя создать больше одного экземпляра данного класса.
Для этого делается приватный конструктор и для доступа к экземпляру класса создается специальный метод, который возвращает только один экземпляр класса.
private ConnectionPool ( ) <
//private constructor
>
private static ConnectionPool instance = null ;
public static ConnectionPool getInstance ( ) <
if ( instance == null )
instance = new ConnectionPool ( ) ;
return instance ;
>
Далее нужно создать метод, через который мы будем получать соединение, но не напрямую, а через пул соединений.
Тут нужно немного комментариев.
Context — это интерфейс, который представляет собой контекст именования, который состоит из набора привязок имени к объекту. InitialContext — класс, который является исходным контекстом для выполнения операций именования. А говоря простым языком, Context, InitialContext позволяют нам иметь доступ к службам имен и каталогов. Для любопытных советую почитать о Java Naming and Directory Interface (JNDI). Метод lookup() извлекает объект по ссылке. В нашем случае он вынимает объект и кастит его к DataSource. Таким образом мы получили DataSource, который вернет нам Connection.
import java.sql.Connection ;
import java.sql.SQLException ;
import javax.naming.Context ;
import javax.naming.InitialContext ;
import javax.naming.NamingException ;
import javax.sql.DataSource ;
public class ConnectionPool <
private ConnectionPool ( ) <
//private constructor
>
private static ConnectionPool instance = null ;
public static ConnectionPool getInstance ( ) <
if ( instance == null )
instance = new ConnectionPool ( ) ;
return instance ;
>
public Connection getConnection ( ) <
Context ctx ;
Connection c = null ;
try <
ctx = new InitialContext ( ) ;
DataSource ds = ( DataSource ) ctx. lookup ( «java:comp/env/jdbc/mydatabase» ) ;
c = ds. getConnection ( ) ;
> catch ( NamingException e ) <
e. printStackTrace ( ) ;
> catch ( SQLException e ) <
e. printStackTrace ( ) ;
>
return c ;
>
>
Это все о пуле соединений. Как всегда, видео, где я все это приконнектил к проекту и ссылка на гитхаб: https://github.com/caligula95/simplewebapp-part4
Пулы соединений к БД — зачем и почему
Теория
Так же можно избежать повторного исполнения шагов два и три если мы будем использовать связанные переменные при написание запросов и кешировать результаты шага три, которые мы получаем от сервера.
В настоящее время большинство драйверов для работы с БД поддерживают работу с пулами соединений. Однако всегда есть соблазн написать свою реализацию, которая будет работать быстрее. Давайте проверим сколько мы выиграем используя пулы соединений и кеширование, как в коробочном решении так и в самописном.
Способ измерения
Для тестов используем свободно распространяемую СУБД PostgreSQL, а клиент напишем на JAVA. В БД создадим небольшую таблицу test.test_table (около 10 строк), состоящую из первичного ключа id и строкового значения value. Пусть у нас клиенты параллельно выполняют запросы к БД, для этого создадим потоки, которые будут делать простые запросы поиска по первичному ключу в этой таблице. При создании потоков мы будем указывать различную реализацию пулов соединений, что позволит нам сравнить производительность, т.к. поток будет считать суммарное время потраченное им на выполнение 100 запросов.
Теперь сделаем несколько пулов, и сравним производительность.
Первым будет классический, который на каждый запрос открывает соединение с сервером и после выполнения запроса закрывающий его.
Вторым будет, использующий специальный кеширующий источник данных класс из JDBC драйвера к PostgreSQL — PGPoolingDataSource. Который позволяет задать размер пула соединений, а так же начальное количество соединений. Кроме того в настройках у PreparedStatement есть настройка setPrepareThreshold — отвечающая за количество выполнений запроса, после которого запрос кешируется и не требует парсинга и построения плана выполнения.
Ну и в конце нашу реализацию пулов, когда мы сами кешируем соединения к БД а также результаты разбора SQL запроса (PreparedStatement).
Так же придётся реализовать свой класс соединения с БД, который будет осуществлять кеширование PreparedStatement.
Плюс свой класс реализующей интерфейс PreparedStatement, и не реагирующий на закрытие
Заключение
Ну и наконец сравним производительность трех различных пулов соединений, запустим тесты с количеством параллельных потоков от 1 до 10, для различных реализаций. В результате получился следующая зависимость общего времени выполнения задачи от количества потоков.
Из графика видно, что кешировать соединения с БД явно нужно, это даёт значительный прирост производительности системы. А вот писать самописную реализацию кеширования соединений и PreparedStatement не даёт ощутимой выгоды.