2015-06-24 5 views
0

У нас есть приложение на основе Spring и недавно мы вступили в производство. Мы используем Spring @Controller, который в конечном итоге попадает в DAO, которые используют JDBCTemplate. Используется c3p0 ComboPooledDataSourcec3p0 DataSource monitor deadlock - все темы зависают - как исправить

При увеличенной нагрузке (что-то вроде 150 одновременных пользователей) приложение зависает для всех пользователей - DataSource блокируется чем-то - на дампе потока есть примерно 200 потоков, которые, DataSource зашел в тупик.

"http-bio-8080-exec-440" - Thread [email protected] 
java.lang.Thread.State: WAITING 
at java.lang.Object.wait(Native Method) 
- waiting on <146d984e> (a com.mchange.v2.resourcepool.BasicResourcePool) 
at com.mchange.v2.resourcepool.BasicResourcePool.awaitAvailable(BasicResourcePool.java:1418) 
at com.mchange.v2.resourcepool.BasicResourcePool.prelimCheckoutResource(BasicResourcePool.java:606) 
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:526) 
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutAndMarkConnectionInUse(C3P0PooledConnectionPool.java:756) 
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:683) 
at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:140) 
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111) 
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77) 
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:573) 
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:637) 
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:666) 
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:674) 
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:718) 

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

В то время C3P0 был настроен так:

После этого я изменил конфигурацию C3P0 как следует - и включена регистрация DEBUG для com.mchange.v2.c3p0 пакета:

app_en.driverClass=com.mysql.jdbc.Driver 
app_en.user=tapp_en 
app_en.password=tapp_en 
app_en.jdbcUrl=jdbc:mysql://10.10.0.102:3306/tapp_en? useUnicode=true&characterEncoding=utf-8&autoReconnect=true 

app_en.acquireIncrement=5 
app_en.maxIdleTime=180 
app_en.maxIdleTimeExcessConnections=60 
app_en.unreturnedConnectionTimeout=30 
app_en.checkoutTimeout=10000 
app_en.numHelperThreads=12 
app_en.debugUnreturnedConnectionStackTraces=true 
app_en.initialPoolSize=10 
app_en.maxPoolSize=100 
app_en.idleConnectionTestPeriod=120 
app_en.preferredTestQuery="select 1 from tbl_users" 

С этой конфигурацией , Я снова запускал тесты нагрузки, и приложение все еще зависало ... хотя потоки восстанавливаются после того, как они не могут получить соединение с базой данных. Несмотря на то, что игра висела для слишком большого количества пользователей, хотя потоки восстановились в отличие от предыдущей конфигурации - поэтому им пришлось перезапустить свои клиенты. Хотя все записи были включены, журналы c3p0 не регистрируют никаких сообщений о блокировке. Сообщения об ошибках, которые я вижу только, что:

[06/24/2015 12:20:54] [C3P0PooledConnectionPoolManager[identityToken->1oed6dl9a9ak8qsgqfvdu|4d6145af]-HelperThread-#10] DEBUG NewPooledConnection - [email protected] closed by a client. 
java.lang.Exception: DEBUG -- CLOSE BY CLIENT STACK TRACE 
at com.mchange.v2.c3p0.impl.NewPooledConnection.close(NewPooledConnection.java:659) 
at com.mchange.v2.c3p0.impl.NewPooledConnection.closeMaybeCheckedOut(NewPooledConnection.java:255) 
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.destroyResource(C3P0PooledConnectionPool.java:621) 
at com.mchange.v2.resourcepool.BasicResourcePool$1DestroyResourceTask.run(BasicResourcePool.java:1024) 
at com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:696) 

Там нет никаких сделок в подаче заявления, ни мы с помощью любого TransactionManager или TransactionTemplate. Интересно, может ли это быть какой-то ошибкой в ​​используемых фреймах или неправильной конфигурации. Это соответствующие структуры, используемые:

c3p0-0.9.5-pre8 
mysql-connector-java-5.1.24 
spring-core-3.2.1.RELEASE 
spring-web-3.2.1.RELEASE 
mchange-commons-java-0.2.7 

Мы действительно ценим любую помощь, потому что это блокирует наши усилия, чтобы выпустить наш продукт.

P.S. EDIT: Вот конфигурация DataSource:

<bean id="app_en_DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" 
    destroy-method="close"> 
    <property name="driverClass" value="${app_en.driverClass}" /> 
    <property name="jdbcUrl" value="${app_en.jdbcUrl}" /> 
    <property name="user" value="${app_en.user}" /> 
    <property name="password" value="${app_en.password}" /> 

    <property name="acquireIncrement" value="${app_en.acquireIncrement}"></property> 
    <property name="maxIdleTime" value="${app_en.maxIdleTime}"></property> 
    <property name="maxIdleTimeExcessConnections" value="${app_en.maxIdleTimeExcessConnections}"></property> 
    <property name="unreturnedConnectionTimeout" value="${app_en.unreturnedConnectionTimeout}"></property> 
    <property name="checkoutTimeout" value="${app_en.checkoutTimeout}"></property> 
    <property name="numHelperThreads" value="${app_en.numHelperThreads}"></property> 
    <property name="debugUnreturnedConnectionStackTraces" value="${app_en.debugUnreturnedConnectionStackTraces}"></property> 
    <property name="initialPoolSize" value="${app_en.initialPoolSize}"></property> 
    <property name="maxPoolSize" value="${app_en.maxPoolSize}"></property> 
    <property name="idleConnectionTestPeriod" value="${app_en.idleConnectionTestPeriod}"></property> 
    <property name="preferredTestQuery" value="${app_en.preferredTestQuery}"></property> 
</bean> 

А вот какой-то код внутри приложения, не используя JdbcTemplate непосредственно. Там нет ничего, что делает это, все остальное jdbcTemplate.update, jdbcTemplate.query:

Connection conn = null; 
    ResultSet getItemsRS = null; 

    try { 
     JdbcTemplate jdbcTemplate = getJdbcTemplate(database); 

     conn = jdbcTemplate.getDataSource().getConnection(); 

     UserItems items; 

     if (!action.areItemsNew()) { 

      conn.setAutoCommit(false); 
      conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); 

      PreparedStatement getItemsPS = conn.prepareStatement("select * from tbl_items where ownerId = ? for update", 
        ResultSet.TYPE_FORWARD_ONLY, 
        ResultSet.CONCUR_UPDATABLE); 
      getItemsPS.setLong(1, userId); 

      getItemsRS = getItemsPS.executeQuery(); 
      getItemsRS.next(); 

      items = new UserItemsRowMapper().mapRow(getItemsRS, getItemsRS.getRow()); 
     } else { 
      items = new UserItems(); 
     } 

     action.doUserItemsAction(items); 

     ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
     ObjectOutputStream oos = new ObjectOutputStream(baos); 
     oos.writeObject(items.getItemContainers()); 
     oos.close(); 
     byte[] data = baos.toByteArray(); 
     Blob blob = conn.createBlob(); 
     blob.setBytes(1, data); 

     if (!action.areItemsNew()) { 
      getItemsRS.updateBlob("data", blob); 
      getItemsRS.updateRow(); 
     } else { 
      jdbcTemplate.update("insert into tbl_items(ownerId,data) values(?,?)", userId, data); 
     } 

    } catch (Exception e) { 
     logger.error(e); 
     throw new RuntimeException(e); 
    } finally { 
     if (!action.areItemsNew()) { 
      try { 
       conn.commit(); 
       conn.close(); 
      } catch (SQLException e) { 
       logger.error(e); 
       throw new RuntimeException(e); 
      } 
     } 
    } 

Причиной этого кода является то, что я хотел бы, чтобы блокировать чтение/запись к элементам пользователя, прежде чем они будут обновлены эта операция action.doUserItemsAction(items) как написано выше.

+0

фотографии эти ссылки http://stackoverflow.com/questions/17272141/websphere-hangs-due-to-c3p0 http://stackoverflow.com/questions/14105932/c3p0-hangs-in-awaitavailable-with -hibernate http://stackoverflow.com/questions/19313103/c3p0-hangs-java-1-6 –

+0

извините, но эти ссылки не дают мне более глубокого понимания проблемы, которую у меня уже есть ... –

+1

Где кода и конфигурации? Также я бы предложил использовать другой пул, а не C3P0, что-то вроде [HikariCP] (https://github.com/brettwooldridge/HikariCP). –

ответ

1

Код, который у вас есть, потенциально опасен и имеет утечку соединения, когда вы сами проверяете соединение, вы всегда должны его закрывать, может быть, он не сможет закрыть соединение.

Вместо этого я настоятельно рекомендую использовать Spring для управления вашими транзакциями и подключениями.

Сначала прокомментируйте свой метод с помощью @Transactional(isolation=SERIALIZABLE).Затем добавьте DataSourceTransactionManager и <tx:annotation-driven /> в вашу конфигурацию. После этих изменений перепишите код доступа к данным, который у вас есть.

JdbcTemplate jdbcTemplate = getJdbcTemplate(database); 
final UserItems items; 
if (!action.areItemsNew()) { 
    items = jdbcTemplate.queryForObject("select * from tbl_items where ownerId = ? for update", userId, new UserItemsRowMapper()); 
} else { 
    items = new UserItems(); 
} 

action.doUserItemsAction(items); 

String query = !action.areItemsNew() ? "update tbl_items set data=? where ownerId=?" : "insert into tbl_items(data,ownerId) values(?,?)"; 

byte[] data = SerializationUtils.serialize(items.getItemContainers()); 
jdbcTemplate.update(query, new SqlLobValue(data), userId); 

Нечто подобное (вместе с вышеупомянутой модификацией должно работать). (Это было более или менее от верхней части головы, так что это может потребовать некоторой настройки). Использование правильного управления транзакциями гарантирует, что все повторно использует одно и то же единственное соединение вместо нескольких соединений, а также гарантирует, что соединение будет возвращено в пул по завершении или когда что-то пойдет не так.

Я бы по-прежнему предлагал другой источник данных, поскольку C3P0 довольно старый.

+0

@ Вы думаете, что этот фрагмент кода обеспечит тот же эффект (блокировка строки до завершения метода?) –

+1

Да, из-за транзакционной демаркации и выбора для обновления. –

+0

Спасибо. Попробуй это прямо сейчас! Я надеюсь, что это решает проблему. –

1

Итак, несколько вещей.

1) Сообщения об ошибках, которые вы видите, не являются ошибками, когда c3p0 регистрирует исключение, сообщение которого начинается с DEBUG, это означает, что вы регистрируетесь на уровнях DEBUG, а c3p0 генерирует исключение только для захвата трассировки стека. (c3p0 - это старая библиотека, Thread.getStackTrace() не существовало в тот же день, создав исключение - это удобный способ захвата и сброса стека). Вы просто регистрируете ожидаемое уничтожение объединенных подключений из-за истечения срока действия или сбоев тестирования. В общем случае c3p0 ожидает входа в INFO, он будет очень многословным на уровнях DEBUG.

2) Вы не блокируете пул потоков c3p0. Если бы вы были, вы увидели бы сообщения APPARENT DEADLOCK и затем восстановили. Вы испытываете состояние истощения пула: клиенты ожидают подключения, но пул находится в maxPoolSize и не может их приобрести.

3) Обычная причина истощения бассейна - это утечка соединения. В некоторых случаях в коде кода приложения, при некоторых (возможно, исключительных) обстоятельствах, Связи приобретаются, а затем никогда не закрываются(). Вы должны быть очень осторожны, чтобы убедиться, что соединения надежно закрыты() в окончательном блоке способами, которые не могут быть пропущены из-за предыдущих сбоев в блоке finally. В Java 7+ используйте try-in-resources. В более старых версиях используйте reliable resource cleanup idiom.

4) Чтобы проверить, является ли проблема утечки соединения, задайте параметры конфигурации c3p0 unreturnedConnectionTimeout и debugUnreturnedConnectionStackTraces. unreturnedConnectionTimeout будет работать вокруг проблемы, но yuck. Что еще более важно, debugUnreturnedConnectionStackTraces покажет вам, где проблема, так что вы можете ее исправить, регистрируя трассировку стека, которая открыла незакрытое Исключение в INFO. (Вы должны установить unreturnedConnectionTimeout для debugUnreturnedConnectionStackTraces иметь никакого эффекта, трассировки стека записывается, когда соединение не будет, как заброшен.)

5) Несмотря на то, 0.9.5-pre8, вероятно, хорошо, текущая версия производства C3P0 является c3p0 -0.9.5.1 (что зависит от mchange-commons-java v.0.2.10). Вы можете подумать об использовании этого. Я не думаю, что это вообще имеет отношение к вашей проблеме, но все же.

Надеюсь, это поможет!

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

} finally { 
    if (conn != null) { 
     try { if (!action.areItemsNew()) conn.commit(); } 
     catch (SQLException e) { 
      logger.error(e); 
      throw new RuntimeException(e); 
     } finally { 
      conn.close() 
     } 
    } 
} 

Update 2: переделано, наконец, блок выше будет решить утечку соединения, но если на вашем месте я бы также изменить логику этого кода относительно commit(). Вот предложенный вариант:

Connection conn = null; 
ResultSet getItemsRS = null; 

try { 
    JdbcTemplate jdbcTemplate = getJdbcTemplate(database); 

    conn = jdbcTemplate.getDataSource().getConnection(); 

    UserItems items; 

    if (!action.areItemsNew()) { 

     conn.setAutoCommit(false); 
     conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); 

     PreparedStatement getItemsPS = conn.prepareStatement("select * from tbl_items where ownerId = ? for update", 
       ResultSet.TYPE_FORWARD_ONLY, 
       ResultSet.CONCUR_UPDATABLE); 
     getItemsPS.setLong(1, userId); 

     getItemsRS = getItemsPS.executeQuery(); 
     getItemsRS.next(); 

     items = new UserItemsRowMapper().mapRow(getItemsRS, getItemsRS.getRow()); 
    } else { 
     items = new UserItems(); 
    } 

    action.doUserItemsAction(items); 

    ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
    ObjectOutputStream oos = new ObjectOutputStream(baos); 
    oos.writeObject(items.getItemContainers()); 
    oos.close(); 
    byte[] data = baos.toByteArray(); 
    Blob blob = conn.createBlob(); 
    blob.setBytes(1, data); 

    if (!action.areItemsNew()) { 
     getItemsRS.updateBlob("data", blob); 
     getItemsRS.updateRow(); 
     conn.commit(); 
    } else { 
     jdbcTemplate.update("insert into tbl_items(ownerId,data) values(?,?)", userId, data); 
    } 
} catch (Exception e) { 
    logger.error(e); 
    throw new RuntimeException(e); 
} finally { 
    try { if (conn != null) conn.close(); } 
    catch (Exception e) 
     { logger.error(e); } 
} 

Теперь commit() будет вызван только if (!action.areItemsNew()) И все ожидаемые операции удалось. До того, как commit() получил бы вызов, даже если что-то пошло не так. Код очистки ресурсов намного проще и чище. Обратите внимание, что в предлагаемой версии, если есть Исключение на close(), оно регистрируется, но оно не завернуто и не отвернуто как исключение RuntimeException. Обычно, если есть исключение на close(), раньше было более информативное исключение, и это тот, который вы хотите увидеть. Если единственное место, где происходит исключение, - это close(), это означает, что все операции с базой данных преуспели, поэтому ваше приложение может продолжить работу, несмотря на ошибку. (Если существует множество Исключений на close(), в конечном итоге вы исчерпаете пул соединений, но на практике это произойдет только в том случае, если с вашей базой данных или сетью есть что-то плохое.)

+0

Я уже установил эти два параметра - unverturnedConnectionTimeout и debugUnreturnedConnectionStackTraces, как я писал в вопросе. Это не дало мне никаких прозрений, кроме того, что они заставляли подключаться к релизу. Ошибка c3p0 не была зарегистрирована даже с debugUnreturnedConnectionStackTraces, я включил ведение журнала DEBUG для пакета 'com.mchange.v2.c3p0'. –

+0

Ох. Таким образом, проблема довольно очевидна в коде, который вы добавили, когда я писал: Есть два пути, по которым соединение может не получиться близко() ed: (1) 'if (! Action.areItemsNew())' вы просто игнорируете закрытие связь. (2), даже если это условие истинно, если 'commit()' выбрасывает исключение, ваш вызов 'close()' будет пропущен. Этот код, скорее всего, будет утечка соединений во времени и использования. –

+0

Войдите в INFO, чтобы увидеть 'debugUnreturnedConnectionStackTraces'; они будут потеряны в слишком большом количестве шума, если вы без разбора заходите на уровни DEBUG. То, что вы наблюдали, «заставляя застревание соединений в выпуске», проверяет проблему. Существуют застрявшие многожильные соединения, потому что при определенных обстоятельствах приложение не может «закрыть»() «Подключения». Исправьте это, и ваша жизнь будет намного лучше! –