ПРОБЛЕМА
Недавно я был назначен ответственным за веб-приложение Java с уже написанным и написанным кодом. Приложение получает умеренно высокий трафик и имеет пиковые часы трафика между 11:00 и 15:00 каждый день. Приложение использует Spring, JPA (Hibernate), MYSQL DB. Spring был настроен для использования пула соединений tomcat jdbc для соединения с БД. (Подробная информация о конфигурации в конце сообщения)
В течение последних нескольких дней во время пиковых нагрузок приложения приложение снижалось из-за того, что tomcat не реагировал на запросы. Он потребовал повторного запуска tomcat несколько раз.
Пройдясь по журналам tomcat catalina.out, я заметил много
Caused by: java.sql.SQLException: Connection has already been closed.
at org.apache.tomcat.jdbc.pool.ProxyConnection.invoke(ProxyConnection.java:117)
at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:109)
at org.apache.tomcat.jdbc.pool.DisposableConnectionFacade.invoke(DisposableConnectionFacade.java:80)
at com.sun.proxy.$Proxy28.prepareStatement(Unknown Source)
at org.hibernate.jdbc.AbstractBatcher.getPreparedStatement(AbstractBatcher.java:505)
at org.hibernate.jdbc.AbstractBatcher.getPreparedStatement(AbstractBatcher.java:423)
at org.hibernate.jdbc.AbstractBatcher.prepareQueryStatement(AbstractBatcher.java:139)
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1547)
at org.hibernate.loader.Loader.doQuery(Loader.java:673)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236)
at org.hibernate.loader.Loader.loadCollection(Loader.java:1994)
... 115 more
Они появляются часто перед катастрофой.
Следуя ранее перед этими исключениями, я заметил, что множество подключений оставлено незадолго до закрытых исключений Connection.
WARNING: Connection has been abandoned PooledConnection[[email protected]]:java.lang.Exception
at org.apache.tomcat.jdbc.pool.ConnectionPool.getThreadDump(ConnectionPool.java:1065)
at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:782)
at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:618)
at org.apache.tomcat.jdbc.pool.ConnectionPool.getConnection(ConnectionPool.java:188)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection(DataSourceProxy.java:128)
at org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider.getConnection(InjectedDataSourceConnectionProvider.java:47)
at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:423)
at org.hibernate.jdbc.ConnectionManager.getConnection(ConnectionManager.java:144)
at org.hibernate.jdbc.AbstractBatcher.prepareQueryStatement(AbstractBatcher.java:139)
Кажется, что они появляются часто перед исключениями, связанными с подключением. И это, по-видимому, первые признаки грядущей гибели в журналах.
АНАЛИЗ
Идя по журналам, я решил посмотреть, есть ли конфигурация пула соединений /mysql, которые могут вызвать проблему. Прошел несколько отличных статей, которые показывают настройку пула для производственной среды. Ссылки 1 и 2
Следуя этим статьям, я заметил, что:
-
В приведенной ниже строке статьи JHanik (ссылка 1) упоминается этот
Установка значения параметра abandonWhenPercentageFull равным 100 означает, что соединения не считаются оставленными, если мы не достигли предела maxActive.
Я подумал, что это может быть важно в моем случае, потому что я вижу, что многие соединения заброшены.
-
Моя настройка max_connections не соответствует тому, что рекомендуется (в ссылке 2)
mysql max_connections должен быть равен max_active + max_idle
ЧТО Я СКАЗАЛ
Итак, согласно рекомендациям статей, я сделал следующие две вещи:
- Изменен отказ отWhenPercentageFull до 100
- На моем сервере MYSQL max_connections был установлен как 500. Увеличено до 600 В настройках пула соединений max_active было 200, а max_idle - 50. Изменено значение max_active = 350, max_idle = 250
ЭТО НЕ ПОМОГАЛ
На следующий день в часы пик были сделаны следующие наблюдения:
- Томкат не спустился. Приложение оставалось в течение пиковых часов. Однако производительность от плохого к худшему, а затем приложение было едва пригодным для использования, хотя оно действительно не снижалось.
- Пул соединений DB, хотя и увеличен в размерах, полностью используется, и я мог видеть 350 активных подключений к БД в какой-то момент.
НАКОНЕЦ, МОЙ ВОПРОС:
Очевидно, что есть проблемы с тем, как происходит соединение БД с сервером приложений. Поэтому у меня есть два направления, чтобы проанализировать этот анализ.
Мой вопрос: какой из них я должен брать?
1. Проблема заключается не в настройках пула соединений. Код - вот что вызывает проблему.
В коде, где соединения DB не закрываются, могут быть места. Это приводит к открытию большого количества подключений.
В коде используется GenericDao, который расширяется в каждом классе Dao. GenericDao использует Spring JpaTemplate для извлечения экземпляра EntityManager, который, в свою очередь, используется для всех операций с БД. Мое понимание заключается в использовании JpaTemplate, который обрабатывает nitty gritty закрытия соединений DB внутри.
Итак, где именно я должен искать возможные утечки соединения?
2. Проблема связана с параметрами конфигурации пула соединений /mysql. Тем не менее, оптимизация, которую я поставил, нуждается в дальнейшей настройке
Если да, на каких параметрах я должен смотреть? Должен ли я собирать некоторые данные для использования, чтобы определить более подходящие значения для моего пула соединений. (Например, для max_active, max_idle, max_connections)
Добавление: полная конфигурация пула соединений
<bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://xx.xx.xx.xx" />
<property name="username" value="xxxx" />
<property name="password" value="xxxx" />
<property name="initialSize" value="10" />
<property name="maxActive" value="350" />
<property name="maxIdle" value="250" />
<property name="minIdle" value="90" />
<property name="timeBetweenEvictionRunsMillis" value="30000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="60" />
<property name="abandonWhenPercentageFull" value="100" />
<property name="testOnBorrow" value="true" />
<property name="validationQuery" value="SELECT 1" />
<property name="validationInterval" value="30000" />
<property name="logAbandoned" value="true" />
<property name="jmxEnabled" value="true" />
</bean>