Tomcat Connection pool создает слишком много соединений, застрял в спящем режиме - PullRequest
21 голосов
/ 27 апреля 2011

Я использую Tomcat 6.0.29 с пулом соединений Tomcat 7 и MySQL.Тестируя мое приложение, оно ничего не использует повторно из пула, а заканчивает тем, что создает новый пул, и в конечном итоге я не могу использовать базу данных, потому что в пуле есть сотни спящих соединений, когда установлен максимальный активный размер для пула20.

См. здесь для справки:

+----+------+-----------------+--------+---------+------+-------+------------------+
| Id | User | Host            | db     | Command | Time | State | Info             |
+----+------+-----------------+--------+---------+------+-------+------------------+
|  2 | root | localhost:51877 | dbname | Sleep   |    9 |       | NULL             |
|  4 | root | localhost       | NULL   | Query   |    0 | NULL  | show processlist |
|  5 | root | localhost:49213 | dbname | Sleep   |   21 |       | NULL             |
|  6 | root | localhost:53492 | dbname | Sleep   |   21 |       | NULL             |
|  7 | root | localhost:46012 | dbname | Sleep   |   21 |       | NULL             |
|  8 | root | localhost:34964 | dbname | Sleep   |   21 |       | NULL             |
|  9 | root | localhost:52728 | dbname | Sleep   |   21 |       | NULL             |
| 10 | root | localhost:43782 | dbname | Sleep   |   21 |       | NULL             |
| 11 | root | localhost:38468 | dbname | Sleep   |   21 |       | NULL             |
| 12 | root | localhost:48021 | dbname | Sleep   |   21 |       | NULL             |
| 13 | root | localhost:54854 | dbname | Sleep   |   21 |       | NULL             |
| 14 | root | localhost:41520 | dbname | Sleep   |   21 |       | NULL             |
| 15 | root | localhost:38112 | dbname | Sleep   |   13 |       | NULL             |
| 16 | root | localhost:39168 | dbname | Sleep   |   13 |       | NULL             |
| 17 | root | localhost:40427 | dbname | Sleep   |   13 |       | NULL             |
| 18 | root | localhost:58179 | dbname | Sleep   |   13 |       | NULL             |
| 19 | root | localhost:40957 | dbname | Sleep   |   13 |       | NULL             |
| 20 | root | localhost:45567 | dbname | Sleep   |   13 |       | NULL             |
| 21 | root | localhost:48314 | dbname | Sleep   |   13 |       | NULL             |
| 22 | root | localhost:34546 | dbname | Sleep   |   13 |       | NULL             |
| 23 | root | localhost:44928 | dbname | Sleep   |   13 |       | NULL             |
| 24 | root | localhost:57320 | dbname | Sleep   |   13 |       | NULL             |
| 25 | root | localhost:54643 | dbname | Sleep   |   29 |       | NULL             |
| 26 | root | localhost:49809 | dbname | Sleep   |   29 |       | NULL             |
| 27 | root | localhost:60993 | dbname | Sleep   |   29 |       | NULL             |
| 28 | root | localhost:36676 | dbname | Sleep   |   29 |       | NULL             |
| 29 | root | localhost:53574 | dbname | Sleep   |   29 |       | NULL             |
| 30 | root | localhost:45402 | dbname | Sleep   |   29 |       | NULL             |
| 31 | root | localhost:37632 | dbname | Sleep   |   29 |       | NULL             |
| 32 | root | localhost:56561 | dbname | Sleep   |   29 |       | NULL             |
| 33 | root | localhost:34261 | dbname | Sleep   |   29 |       | NULL             |
| 34 | root | localhost:55221 | dbname | Sleep   |   29 |       | NULL             |
| 35 | root | localhost:39613 | dbname | Sleep   |   15 |       | NULL             |
| 36 | root | localhost:52908 | dbname | Sleep   |   15 |       | NULL             |
| 37 | root | localhost:56401 | dbname | Sleep   |   15 |       | NULL             |
| 38 | root | localhost:44446 | dbname | Sleep   |   15 |       | NULL             |
| 39 | root | localhost:57567 | dbname | Sleep   |   15 |       | NULL             |
| 40 | root | localhost:56445 | dbname | Sleep   |   15 |       | NULL             |
| 41 | root | localhost:39616 | dbname | Sleep   |   15 |       | NULL             |
| 42 | root | localhost:49197 | dbname | Sleep   |   15 |       | NULL             |
| 43 | root | localhost:59916 | dbname | Sleep   |   15 |       | NULL             |
| 44 | root | localhost:37165 | dbname | Sleep   |   15 |       | NULL             |
| 45 | root | localhost:45649 | dbname | Sleep   |    1 |       | NULL             |
| 46 | root | localhost:55397 | dbname | Sleep   |    1 |       | NULL             |
| 47 | root | localhost:34322 | dbname | Sleep   |    1 |       | NULL             |
| 48 | root | localhost:54387 | dbname | Sleep   |    1 |       | NULL             |
| 49 | root | localhost:55147 | dbname | Sleep   |    1 |       | NULL             |
| 50 | root | localhost:47280 | dbname | Sleep   |    1 |       | NULL             |
| 51 | root | localhost:56856 | dbname | Sleep   |    1 |       | NULL             |
| 52 | root | localhost:58369 | dbname | Sleep   |    1 |       | NULL             |
| 53 | root | localhost:33712 | dbname | Sleep   |    1 |       | NULL             |
| 54 | root | localhost:44315 | dbname | Sleep   |    1 |       | NULL             |
| 55 | root | localhost:54649 | dbname | Sleep   |   14 |       | NULL             |
| 56 | root | localhost:41202 | dbname | Sleep   |   14 |       | NULL             |
| 57 | root | localhost:59393 | dbname | Sleep   |   14 |       | NULL             |
| 58 | root | localhost:38304 | dbname | Sleep   |   14 |       | NULL             |
| 59 | root | localhost:34548 | dbname | Sleep   |   14 |       | NULL             |
| 60 | root | localhost:49567 | dbname | Sleep   |   14 |       | NULL             |
| 61 | root | localhost:48077 | dbname | Sleep   |   14 |       | NULL             |
| 62 | root | localhost:48586 | dbname | Sleep   |   14 |       | NULL             |
| 63 | root | localhost:45308 | dbname | Sleep   |   14 |       | NULL             |
| 64 | root | localhost:43169 | dbname | Sleep   |   14 |       | NULL             |

Он создает ровно 10 для каждого запроса, который является атрибутом minIdle & InitialSize, как показано ниже.

ЗдесьПример тестового кода, встроенного в страницу JSP.Код - это не код в моем приложении, а просто используемый для определения, была ли проблема с моим кодом, но проблема все еще сохраняется.

Context envCtx;
envCtx = (Context) new InitialContext().lookup("java:comp/env");
DataSource datasource = (DataSource) envCtx.lookup("jdbc/dbname");
Connection con = null;

try {
  con = datasource.getConnection();
  Statement st = con.createStatement();
  ResultSet rs = st.executeQuery("select * from UserAccount");
  int cnt = 1;
  while (rs.next()) {
      out.println((cnt++)+". Token:" +rs.getString("UserToken")+
        " FirstName:"+rs.getString("FirstName")+" LastName:"+rs.getString("LastName"));
  }
  rs.close();
  st.close();
} finally {
  if (con!=null) try {con.close();}catch (Exception ignore) {}
}

Вот мой файл context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Resource name="jdbc/dbname" 
              auth="Container" 
              type="javax.sql.DataSource" 
              factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
              testWhileIdle="true"
              testOnBorrow="true"
              testOnReturn="false"
              validationQuery="SELECT 1"
              validationInterval="30000"
              timeBetweenEvictionRunsMillis="30000"
              maxActive="20" 
              minIdle="10" 
              maxWait="10000" 
              initialSize="10"
              removeAbandonedTimeout="60"
              removeAbandoned="true"
              logAbandoned="true"
              minEvictableIdleTimeMillis="30000" 
              jmxEnabled="true"
              jdbcInterceptors=
"org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
              username="" 
              password="" 
              driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/dbname?autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>

<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>META-INF/context.xml</WatchedResource>
</Context>

Я уверен, что я могу использовать removeAbandonedTimeout для меньшего числа, и это очистит все эти спящие соединения, но это не решит реальную проблему, не так ли?Кто-нибудь знает, что я делаю не так?Большое спасибо.

Ответы [ 7 ]

11 голосов
/ 23 декабря 2011

У меня нет среды для проверки этого, однако на данный момент я считаю , что вы должны закрывать свой Connection, Statement и ResultSet после каждого запроса;в случае утечки это может привести к зависанию Соединения в состоянии ожидания (но не обязательно возвращенному в пул).

Получаемый объект Соединения на самом деле должен быть своего рода прокси из уровня пула;вызов close для него освобождает ваше «резервирование» для этого соединения и возвращает его в пул.(Это не обязательно закроет базовое, фактическое соединение с базой данных.)

Поскольку оно может оставаться открытым (обычно будет), незакрытые выражения или ResultSets могут интерпретироваться уровнем пула как признак того, что они по-прежнему «занят. »

Вы можете проверить (например, отладчик делает это легко) объект Connection, чтобы определить его состояние во время выполнения, чтобы подтвердить это.

Для простоты (…) мыиспользовал следующую неприятную небольшую подпрограмму в блоках finally после каждого вызова соединения с базой данных: … finally { closeAll (rs, st, con); }, гарантируя, что они немедленно выпадут из контекста.

    /**
 * Close a bunch of things carefully, ignoring exceptions. The
 * “things” supported, thus far, are:
 * <ul>
 * <li>JDBC ResultSet</li>
 * <li>JDBC Statement</li>
 * <li>JDBC Connection</li>
 * <li>Lock:s</li>
 * </ul>
 * <p>
 * This is mostly meant for “finally” clauses.
 *
 * @param things A set of SQL statements, result sets, and database
 *            connections
 */
public static void closeAll (final Object... things) {
    for (final Object thing : things) {
        if (null != thing) {
            try {
                if (thing instanceof ResultSet) {
                    try {
                        ((ResultSet) thing).close ();
                    } catch (final SQLException e) {
                        /* No Op */
                    }
                }
                if (thing instanceof Statement) {
                    try {
                        ((Statement) thing).close ();
                    } catch (final SQLException e) {
                        /* No Op */
                    }
                }
                if (thing instanceof Connection) {
                    try {
                        ((Connection) thing).close ();
                    } catch (final SQLException e) {
                        /* No Op */
                    }
                }
                if (thing instanceof Lock) {
                    try {
                        ((Lock) thing).unlock ();
                    } catch (final IllegalMonitorStateException e) {
                        /* No Op */
                    }
                }
            } catch (final RuntimeException e) {
                /* No Op */
            }
        }
    }
}

Это был просто синтаксический сахар, чтобы никто не могзабыл вставить более длинную и уродливую строфу if (null != con) { try { con.close () } catch (SQLException e) {} } (обычно повторяется три раза для ResultSet, Statement и Connection);и удалил «визуальный шум» того, что наш форматер превратил бы в полный экран случайного кода очистки для каждого блока кода, который коснулся базы данных.

(поддержка Lock была для некоторых связанных,но неприятные состояния взаимоблокировки для потенциальных исключений, которые вообще не имеют ничего общего с базой данных, но мы использовали аналогичным образом для уменьшения шума линии в некотором коде синхронизации потоков. Это от MMO-сервера, который может4000 активных потоков одновременно пытаются манипулировать игровыми объектами и таблицами SQL.)

2 голосов
/ 23 декабря 2011

Просмотрите свойство maxAge пула подключений. (Я заметил, что вы не установили его.)

maxAge

Время в миллисекундах, чтобы сохранить это соединение. Когда соединение вернувшись в пул, пул проверит, если сейчас - время, когда соединение> maxAge было достигнуто, и если это так, он закрывается соединение, а не возвращение его в пул. Значение по умолчанию 0, что означает, что соединения останутся открытыми и возраст не будет проверка будет выполнена после возврата соединения в пул. [источник]

По сути, это позволяет восстановить ваши спящие потоки и решить вашу проблему.

1 голос
/ 22 апреля 2012

Краткое примечание к вашему коду: не только Connection, но и ResultSet и Statement также должны быть закрыты в блоке finally. Метод, заданный BRPocock, должен работать нормально.

Но это не фактическая причина ваших 10 подключений за запрос! Причина, по которой вы получаете 10 соединений на каждый запрос, заключается в том, что вы установили minIdle равным 10, что означает, что вы заставляете каждый источник данных иметь 10 соединений при его создании. (Попробуйте установить minIdle на 5, и вы увидите, что у вас будет 5 подключений на запрос.)

Проблема в вашем случае заключается в том, что каждый раз, когда вы делаете запрос, вы создаете новый источник данных:

DataSource datasource = (DataSource) envCtx.lookup("jdbc/dbname");

Я не уверен, как именно работает поиск, но, учитывая ваш список процессов из mysql, я довольно уверен, что для каждого запроса вы создаете новый источник данных. Если у вас есть Java-сервлет, то вы должны создать DataSource в методе init () вашего основного сервлета. Оттуда вы можете получить соединения от него.

В моем случае я сделал что-то еще, потому что у меня есть несколько источников данных (несколько баз данных), я использую следующий код для получения моего источника данных:

private DataSource getDataSource(String db, String user, String pass)
{
    for(Map.Entry<String, DataSource> entry : datasources.entrySet())
    {
        DataSource ds = entry.getValue();
        if(db.equals(ds.getPoolProperties().getUrl()))
        {
            return ds;
        }
    }
    System.out.println("NEW DATASOURCE CREATED ON REQUEST: " + db);

    DataSource ds = new DataSource(initPoolProperties(db, user, pass));
    datasources.put(db, ds);
    return ds;
}

Источник данных опирается на метод equals, который не очень быстрый, но да, он работает. Я просто храню глобальный HashMap, содержащий мои источники данных, и если я запрашиваю источник данных, который еще не существует, я создаю новый. Я знаю, что это работает очень хорошо, потому что в журналах я вижу сообщение NEW DATASOURCE CREATED ON REQUEST: dbname только один раз на базу данных, даже несколько клиентов используют один и тот же источник данных.

1 голос
/ 24 декабря 2011

возможно, это примечание из документации по пулу соединений dbcp может быть ответом:

ПРИМЕЧАНИЕ. Если maxIdle слишком низко установлен на загруженных системах, возможно, вы увидите, что соединения закрываются и почти сразуновые соединения открываются.Это является результатом того, что активные потоки на мгновение закрывают соединения быстрее, чем они их открывают, в результате чего число незанятых соединений поднимается выше maxIdle.Наилучшее значение для maxIdle для сильно загруженной системы будет варьироваться, но по умолчанию это хорошая отправная точка.

возможно, maxIdle должен == maxActive + minIdle для вашей системы.

0 голосов
/ 06 мая 2016

Это происходит из-за перезагрузки вашего приложения без Resource Killing. И ваш ресурс контекста приложения все еще жив. Сейчас есть возможность решить эту проблему, если вы не удалите / Catalina / localhost / .xml и отложите его или сделаете более частый перезапуск службы с помощью :: service tomcat7 restart

ПРИМЕЧАНИЕ :: Ничего плохого в вашем коде, ничего плохого в вашей конфигурации ..

развеселить ~

0 голосов
/ 09 декабря 2014

У меня была эта проблема, потому что я использовал Hibernate и не смог аннотировать некоторые мои методы с помощью @Transactional.Соединения никогда не возвращались в пул.

0 голосов
/ 28 декабря 2011

Вам следует попробовать с поставщиком соединений создать класс, который будет содержать ваш поставщик источника данных, объявленный как статический, вместо того, чтобы искать его при каждом вызове.То же самое для вашего InitialContext.Может быть, это потому, что вы каждый раз создаете новый экземпляр.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...