Подтвердить что ты не робот

Бассейн Tomcat Connection создает слишком много соединений, застрял в спящем режиме

Я использую 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 на низком уровне, и он очистит все эти спящие соединения, но это не повлияет на реальную проблему? Кто-нибудь знает, что я делаю неправильно? Большое вам спасибо.

4b9b3361

Ответ 1

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

Получаемый вами объект Connection должен быть своего рода прокси-сервером на уровне объединения; вызов 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

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

maxAge

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

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

Ответ 3

возможно, эта заметка из dbcp-пула docs может быть ответом:

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

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

Ответ 4

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

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

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

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

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

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

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 только один раз на базу данных, даже несколько клиентов используют один и тот же источник данных.

Ответ 5

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

Ответ 6

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

Ответ 7

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

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

cheer ~