2013-05-23 12 views
6

Ho un'applicazione Tomcat che utilizza MySQL e Hibernate per ORM. La natura della nostra applicazione richiede che dobbiamo estrarre e aggregare molti dati di analisi da un archivio NoSQL per ogni richiesta, quindi abbiamo suddiviso l'estrazione e l'aggregazione per ogni richiesta in più attività e delegato a un servizio executor pool-pool.Timeout della connessione Hibernate/MySQL - Tentativo di gestire gli executor pool di thread non rilasciando le connessioni di Hibernate a C3P0 dopo l'uscita

Quando ogni thread esegue un'attività, ha bisogno di interrogare/aggiornare MySQL su alcune cose, in modo da prendere in prestito le sessioni di Hibernate da C3P0 (che usiamo per il pooling delle connessioni).

config Essential:

<property name="current_session_context_class">thread</property> 
    <property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property> 
    <property name="hibernate.connection.shutdown">true</property> 
    <property name="hibernate.use_sql_comments">false</property> 

<!-- C3p0 Performance Improvements --> 
    <property name="hibernate.c3p0.acquire_increment">1</property> 
    <property name="hibernate.c3p0.idle_test_period">300</property> 
    <property name="hibernate.c3p0.maxConnectionAge">3600</property> 
    <property name="hibernate.c3p0.timeout">120</property> 
    <property name="hibernate.c3p0.max_size">300</property> 
    <property name="hibernate.c3p0.min_size">1</property> 
    <property name="hibernate.c3p0.max_statements">100</property> 
    <property name="hibernate.c3p0.preferredTestQuery">select 1;</property> 

Il problema è le richieste di Hibernate causare l'errore di timeout di connessione/JDBC MySQL dopo 8 ore (il nostro valore configurato del parametro wait_timeout di MySQL è il default cioè 8 ore). Ho replicato questo modificando wait_timeout a 11 minuti, ma il risultato è lo stesso per un wait_timeout 8 ore così:

2013-01-27 20:08:00,088 ERROR [Thread-0] (JDBCExceptionReporter.java:234) - Communications link failure 

The last packet successfully received from the server was 665,943 milliseconds ago. The last packet sent successfully to the server was 6 milliseconds ago. 
org.hibernate.exception.JDBCConnectionException: could not execute query 
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:99) 
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66) 
    at org.hibernate.loader.Loader.doList(Loader.java:2536) 
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2276) 
    at org.hibernate.loader.Loader.list(Loader.java:2271) 
    at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:119) 
    at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1716) 
    at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:347) 
    ..... 
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure 
The last packet successfully received from the server was 665,943 milliseconds ago. The last packet sent successfully to the server was 6 milliseconds ago. 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) 
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) 
    at java.lang.reflect.Constructor.newInstance(Unknown Source) 
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:411) 
    at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1116) 
    at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3102) 
    at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2991) 
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3532) 
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2002) 
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2163) 
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2624) 
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2127) 
    at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:2293) 
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeQuery(NewProxyPreparedStatement.java:76) 
    at org.hibernate.jdbc.AbstractBatcher.getResultSet(AbstractBatcher.java:208) 
    at org.hibernate.loader.Loader.getResultSet(Loader.java:1953) 
    at org.hibernate.loader.Loader.doQuery(Loader.java:802) 
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274) 
    at org.hibernate.loader.Loader.doList(Loader.java:2533) 
    ... 9 more 
Caused by: java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost. 
    at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:2552) 
    at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3002) 
    ... 22 more 

2013-01-27 20:19:00,179 WARN [Thread-0] (NewPooledConnection.java:487) - [c3p0] Another error has occurred [ com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 1,326,037 milliseconds ago. The last packet sent successfully to the server was 660,100 milliseconds ago. is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem. ] which will not be reported to listeners! 
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 1,326,037 milliseconds ago. The last packet sent successfully to the server was 660,100 milliseconds ago. is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem. 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) 
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) 
    at java.lang.reflect.Constructor.newInstance(Unknown Source) 
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:411) 
    at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1116) 
    at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3364) 
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1983) 
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2163) 
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2624) 
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2127) 
    at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:2293) 
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeQuery(NewProxyPreparedStatement.java:76) 
    at org.hibernate.jdbc.AbstractBatcher.getResultSet(AbstractBatcher.java:208) 
    at org.hibernate.loader.Loader.getResultSet(Loader.java:1953) 
    at org.hibernate.loader.Loader.doQuery(Loader.java:802) 
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274) 
    at org.hibernate.loader.Loader.doList(Loader.java:2533) 
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2276) 
    at org.hibernate.loader.Loader.list(Loader.java:2271) 
    at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:119) 
    at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1716) 
    at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:347) 
    .... 
Caused by: java.net.SocketException: Broken pipe 
    at java.net.SocketOutputStream.socketWrite0(Native Method) 
    at java.net.SocketOutputStream.socketWrite(Unknown Source) 
    at java.net.SocketOutputStream.write(Unknown Source) 
    at java.io.BufferedOutputStream.flushBuffer(Unknown Source) 
    at java.io.BufferedOutputStream.flush(Unknown Source) 
    at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3345) 
    ... 20 more 

ho pensato C3P0 non evicting connessioni stantio abbastanza frequentemente, in modo che durante il carico pesante una connessione sarebbe tornato in piscina, andare stantio e essere nuovamente preso in prestito prima dello sfratto. Così ho creato un file c3p0.properties e impostato "c3p0.testConnectionsOnCheckout" su true in modo che nessuna connessione stantia venga presa in prestito dal pool. Ho avuto di nuovo lo stesso errore.

Ho calcolato che poiché le sessioni di sospensione sono configurate con "thread" contesto di sessione, le sessioni utilizzate da un thread non vengono rilasciate fino a quando non viene eseguito il commit o il rollback di una transazione [http://docs.jboss.org/hibernate/orm/3.6/javadocs/org/hibernate/context/ThreadLocalSessionContext.html]. Quindi, l'unica spiegazione che ho è che i thread dell'esecutore eseguono chiamate DB di sola lettura in cui non è in corso alcuna commit, e una volta che l'attività è terminata, il thread ritorna al pool, mantenendo la sessione con esso.

Cosa devo fare qui? La nostra applicazione ha il codice di interrogazione in ibernazione all'interno delle classi Data-Access-Object, una per ogni tipo di bean. Desidero evitare di dover cambiare ogni singolo metodo in quelle classi che effettua chiamate DB di sola lettura in modo da farle "commit" inutilmente. Desidero anche ridurre al minimo le modifiche alla logica aziendale della nostra app.

Esiste un modo per controllare la freschezza della sessione restituita da sessionFactory.getCurrentSession() ogni volta che viene dichiarato/utilizzato un oggetto di accesso ai dati (tutte le classi Data-Access-Object ereditano alcune cose da un classe base singola, quindi posso cambiare le cose nel costruttore della classe base), e magari restituire sessioni stantie al pool? O c'è un modo migliore per farlo?

Grazie.

risposta

0

NOTA: il seguente è basato su un esempio Oracle, ma può essere modificato per MySQL.

Ho risolto il problema delle connessioni interrotte utilizzando le connessioni basate su database Tomcat. Questo può essere fatto creando un file context.xml nella directory META-INF, qualcosa di simile:

<Context crossContext="true" docBase="myBase" path="/myBase" reloadable="false" useHttpOnly="true"> 
    <ResourceLink global="jdbc/dbOne" name="jdbc/dbOne" type="javax.sql.DataSource" /> 
    <!-- Need another DB? --> 
    <!-- <ResourceLink global="jdbc/dbTwo" name="jdbc/dbTwo" type="javax.sql.DataSource" /> --> 
</Context> 

per creare la connessione tuo file server.xml Tomcat deve essere aggiornato. Il sotto è basato su Tomcat 6.0.26 .:

<!-- Global JNDI resources 
    Documentation at /docs/jndi-resources-howto.html 
--> 
<GlobalNamingResources> 
<!-- Editable user database that can also be used by 
    UserDatabaseRealm to authenticate users 
--> 
<Resource name="UserDatabase" auth="Container" 
      type="org.apache.catalina.UserDatabase" 
      description="User database that can be updated and saved" 
      factory="org.apache.catalina.users.MemoryUserDatabaseFactory" 
      pathname="conf/tomcat-users.xml" /> 

    <!-- START MY MODS --> 
    <!-- Add the db connection(s)! The rest is standard in the server.xml file.-->   
    <Resource auth="Container" driverClassName="oracle.jdbc.driver.OracleDriver" initialSize="2" logAbandoned="true" maxActive="5" maxIdle="2" maxWait="120000" minEvictableIdleTimeMillis="1800000" minIdle="1" name="jdbc/dbOne" numTestsPerEvictionRun="3" password="openPlease" removeAbandoned="true" removeAbandonedTimeout="60" testOnBorrow="true" testOnReturn="true" testWhileIdle="true" timeBetweenEvictionRunsMillis="900000" type="javax.sql.DataSource" url="jdbc:oracle:thin:@servername:portnumber:schema" username="myUser" validationQuery="select sysdate from dual"/> 
    <!-- Need another connection? Copy and past the above making the required changes. --> 
    <!-- END MY MODS --> 

Poi nel mio hibernate.cfg.file xml ho specificato:

<!-- Connection handling --> 
<property name="connection.datasource">java:/comp/env/jdbc/dbOne</property> 
<property name="dialect">org.hibernate.dialect.Oracle10gDialect</property> 
<property name="connection.autoReconnect">true</property> 
<property name="connection.autoReconnectForPools">true</property> 
<property name="connection.is-connection-validation-required">true</property> 
<property name="current_session_context_class">thread</property> 

Detto questo, la seguente configurazione utilizzando solo C3P0 ha lavorato anche per me, ma ora preferisco Tomcat connessioni gestite in quanto possono essere condivisi tra le applicazioni sul mio server Tomcat, che è auspicabile nel mio caso.

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE hibernate-configuration PUBLIC 
     "-//Hibernate/Hibernate Configuration DTD 3.0//EN" 
     "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> 
<hibernate-configuration> 
    <session-factory> 
     <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property> 
     <property name="hibernate.connection.password">@[email protected]</property> 
     <property name="hibernate.connection.url">@[email protected]</property> 
     <property name="hibernate.connection.username">someUser</property> 
     <property name="dialect">org.hibernate.dialect.Oracle10gDialect</property> 

     <property name="show_sql">false</property> 
     <property name="format_sql">false</property> 
     <property name="current_session_context_class">thread</property> 

     <!-- Connection handling --> 
     <property name="connection.autoReconnect">true</property> 
     <property name="connection.autoReconnectForPools">true</property> 
     <property name="connection.is-connection-validation-required">true</property> 
     <property name="current_session_context_class">thread</property> 
     <property name="max_fetch_depth">1</property> 

     <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property> 
     <property name="hibernate.c3p0.breakAfterAcquireFailure">false</property> 
     <property name="hibernate.c3p0.acquireRetryAttempts">-1</property> 
     <property name="hibernate.c3p0.acquireRetryDelay">3000</property> 
     <property name="hibernate.c3p0.automaticTestTable">C3P0_TEST</property> <!-- c3p0 uses this table for testing connection. --> 
     <property name="hibernate.c3p0.initialPoolSize">2</property> <!-- 3 is c3p0 default. --> 
     <property name="hibernate.c3p0.minPoolSize">2</property> <!-- 1 is Hibernate default. --> 
     <property name="hibernate.c3p0.maxPoolSize">6</property> <!-- 100 is Hibernate default. --> 
     <property name="hibernate.c3p0.acquireIncrement">2</property> <!-- 3 is c3p0 default. --> 
     <property name="hibernate.c3p0.maxIdleTimeExcessConnections">600</property> <!-- 0 seconds is c3p0 default and means do not check. This is the num of seconds a connections in excess of minPoolSize are permitted to remain idle in the pool before being culled. --> 
     <property name="hibernate.c3p0.idleConnectionTestPeriod">30</property> <!-- 0 seconds is the c3p0 default and means do not check. (i.e. never test). Using only this provides the best performance even if individuals may occasionally receive an error message due to a connection being dropped. --> 

     <!-- Cache setup --> 
     <property name="hibernate.cache.use_second_level_cache">true</property> 
     <property name="hibernate.cache.use_query_cache">true</property> 
     <property name="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.EhCacheRegionFactory</property> 
     <!-- <property name="hibernate.cache.provider_class">net.sf.ehcache.hibernate.SingletonEhCacheProvider</property> --> 

     <property name="cache.use_minimal_puts">true</property> 
     <property name="hibernate.generate_statistics">true</property> 
     <property name="hibernate.cache.use_structured_entries">true</property> 

     <!-- Mapping files --> 
     ... 

     </session-factory> 

</hibernate-configuration>