2011-03-08 33 views
9

Estoy ejecutando un Spring/Hibernate que se conecta a la configuración de MySQL utilizando c3p0 como mi grupo de conexiones. Por alguna razón extraña, se queda sin conexiones cuando el sistema está bajo carga (por supuesto).¡Se están quedando sin conexiones DB!

El sitio era bastante estable hasta que comenzamos a alcanzar un nuevo nivel de tráfico (más de cien usuarios concurrentes). En ese punto, la base de datos se fundiría (vincularía la CPU). Mi primera acción fue en la aplicación para mejorar el rendimiento mediante el almacenamiento en caché y la optimización exhaustiva de consultas, etc.

Ahora simplemente se quedarán sin conexiones de forma intermitente. Ni siquiera parece depender de la carga. Más a tiempo, lo que me hace pensar que es una filtración, pero por mi vida no puedo entender de dónde vendría.

WARN [2011-03-07 17:19:42,409] [TP-Processor38] (JDBCExceptionReporter.java:100) - SQL Error: 0, SQLState: null 
ERROR [2011-03-07 17:19:42,409] [TP-Processor38] (JDBCExceptionReporter.java:101) - An attempt by a client to checkout a Connection has timed out. 
ERROR [2011-03-07 17:19:42,410] [TP-Processor38] (HttpHeadFilter.java:46) - There was a problem passing thru filter:/is-this-guy-crazy-or-just-a-huge-dancing-with-the-stars-fan 
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.hibernate.exception.GenericJDBCException: could not execute query 
     at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:659) 
     at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:552) 
     at javax.servlet.http.HttpServlet.service(HttpServlet.java:617) 
     at javax.servlet.http.HttpServlet.service(HttpServlet.java:717) 
     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) 
     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) 
     at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:343) 
     at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:109) 

Caused by: java.sql.SQLException: An attempt by a client to checkout a Connection has timed out. 
    at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:106) 
    at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:65) 
    at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:527) 
    at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:128) 

Aquí está mi configuración:

<bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy"> 
     <property name="targetDataSource" ref="rootDataSource" /> 
    </bean> 
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
     <property name="mappingLocations" value="classpath:hibernate-mapping.xml" /> 
     <property name="hibernateProperties"> 
      <props> 
       <prop key="hibernate.connection.provider_class">net.sf.hibernate.connection.C3P0ConnectionProvider</prop> 
       <prop key="hibernate.dialect">${hibernate.dialect}</prop> 
       <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> 
       <prop key="hibernate.cache.use_second_level_cache">true</prop> 
       <prop key="hibernate.cache.use_query_cache">true</prop> 
       <prop key="hibernate.cache.generate_statistics">true</prop> 
       <prop key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</prop> 
       <prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop> 
       <prop key="hibernate.connection.zeroDateTimeBehavior">convertToNull</prop> 
       <prop key="hibernate.bytecode.use_reflection_optimizer">${hibernate.bytecode.use_reflection_optimizer}</prop> 
       <!--<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>--> 
       <prop key="hibernate.jdbc.batch_size">${hibernate.jdbc.batch_size}</prop> 

       <!--Actually, it seems the following property affects batch size (or explicit per relationship in the mapping)--> 
       <!--<prop key="hibernate.default_batch_fetch_size">${hibernate.jdbc.batch_size}</prop>--> 
      </props> 
     </property> 
     <property name="dataSource" ref="dataSource" /> 
    </bean> 

    <bean id="rootDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 
     <property name="driverClass" value="${jdbc.driver}" /> 
     <property name="jdbcUrl" value="${jdbc.url}" /> 
     <property name="user" value="${jdbc.username}" /> 
     <property name="password" value="${jdbc.password}" /> 
     <property name="initialPoolSize" value="20" /> 
     <property name="maxPoolSize" value="200" /> 
     <property name="checkoutTimeout" value="30000" /> 
     <property name="maxStatements" value="180" /> 

     <property name="minPoolSize"> 
      <value>${hibernate.c3p0.minPoolSize}</value> 
     </property> 
     <property name="acquireRetryAttempts"> 
      <value>${hibernate.c3p0.acquireRetryAttempts}</value> 
     </property> 
     <property name="acquireIncrement"> 
      <value>${hibernate.c3p0.acquireIncrement}</value> 
     </property> 
     <property name="idleConnectionTestPeriod"> 
      <value>${hibernate.c3p0.idleConnectionTestPeriod}</value> 
     </property> 
     <property name="maxIdleTime"> 
      <value>${hibernate.c3p0.maxIdleTime}</value> 
     </property> 
     <property name="maxIdleTimeExcessConnections"> 
      <value>${hibernate.c3p0.maxIdleTimeExcessConnections}</value> 
     </property> 
     <property name="maxConnectionAge"> 
      <value>${hibernate.c3p0.maxConnectionAge}</value> 
     </property> 
     <property name="preferredTestQuery"> 
      <value>${hibernate.c3p0.preferredTestQuery}</value> 
     </property> 
     <property name="testConnectionOnCheckin"> 
      <value>${hibernate.c3p0.testConnectionOnCheckin}</value> 
     </property> 
     <property name="numHelperThreads"> 
      <value>${hibernate.c3p0.numHelperThreads}</value> 
     </property> 
     <property name="unreturnedConnectionTimeout"> 
      <value>${hibernate.c3p0.unreturnedConnectionTimeout}</value> 
     </property> 
     <property name="debugUnreturnedConnectionStackTraces"> 
      <value>${hibernate.c3p0.debugUnreturnedConnectionStackTraces}</value> 
     </property> 
     <property name="automaticTestTable"> 
      <value>${hibernate.c3p0.automaticTestTable}</value> 
     </property> 
    </bean> 
    hibernate.c3p0.acquireIncrement=5 
hibernate.c3p0.minPoolSize=20 
hibernate.c3p0.acquireRetryAttempts=30 
hibernate.c3p0.idleConnectionTestPeriod=3600 
hibernate.c3p0.maxIdleTime=7200 
hibernate.c3p0.maxIdleTimeExcessConnections=1800  
hibernate.c3p0.maxConnectionAge=14400 
hibernate.c3p0.preferredTestQuery=select 1; 
hibernate.c3p0.testConnectionOnCheckin=false 
hibernate.c3p0.numHelperThreads=6 
hibernate.c3p0.unreturnedConnectionTimeout=0 
hibernate.c3p0.debugUnreturnedConnectionStackTraces=true 
hibernate.c3p0.automaticTestTable=test_connection; 

Me postulo OpenSessionInViewInterceptor que debe ser el cierre de las conexiones:

<bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor"> 
    <property name="sessionFactory"> 
     <ref bean="sessionFactory" /> 
    </property> 
    <property name="flushModeName"> 
     <value>FLUSH_AUTO</value> 
    </property> 

</bean> 

También estoy usando las anotaciones de primavera para @Transactional desde que reutilizar mi servicios en código sin web.

En realidad, aquí solo hay dos opciones, no liberar conexiones cuando haya terminado. O pasar el rato charlando en el DB como si estuviera tratando de meterse en sus pantalones. Si alguien tiene alguna idea le agradecería THX

SEGUIMIENTO: Al final resulta que tenía una fuga de conexiones debido a la utilización de OpenSessionInViewInterceptor. Tenía la seguridad de primavera funcionando como un filtro para que se conectara al DB y nunca los cerrara. La solución era mover el OpenSessionInViewInterceptor a OpenSessionInViewFilter.

Respuesta

5

Es poco probable que @Transactional pierda conexiones; de lo contrario, su sitio dejaría de funcionar después de las primeras 100 solicitudes.

Pero hay otra razón por la que esto sucede:

Tal vez usted ha establecido un tiempo de espera para las conexiones "muertas" y algunas consultas tardan más que eso. Eso significa que su grupo eliminó una conexión ocupada como "muerta" de la agrupación y solicita otra desde la base de datos, hasta que la base de datos quite el conector.

Para depurar esto, habilite el registro para su grupo de conexiones, para que pueda ver cuándo solicita conexiones nuevas.

+0

Mi maxConnectionAge se estableció en 14.400, que de acuerdo con la documentación está en segundos (no ms) por lo que sería de 240 minutos. Definitivamente intentaré encender el registro. El problema es que abre y cierra una TONELADA de conexiones, por lo que es difícil aislar dónde está sucediendo. Especialmente bajo carga. – matsientst

12

Intente habilitar el registro y establecer la propiedad c3p0.debugUnreturnedConnectionStackTraces en verdadero. También establezca c3p0.unreturnedConnectionTimeout en algo más pequeño que el tiempo de consulta promedio (1 seg?). Entonces, cualquier cosa que tome más tiempo que el tiempo de espera registrará un seguimiento de la pila. Esto debería permitirle reducir las cosas bastante rápido.

Si no hay ningún patrón en los restos de la pila, podría ser que su grupo sea demasiado pequeño. Dijiste 100 usuarios simultáneos, pero ¿alguna idea de cuántas consultas por segundo se trata? Si se trata de 100 consultas por segundo y tiene 20 conexiones, entonces cada ejecución sql necesita tomar menos de 200 ms (20 conexiones => 20 segundos totales de trabajo por segundo de tiempo de reloj de pared para hacer 100 consultas).

+0

+1 por utilizar un tiempo de espera menor para rastrearlo más rápido –

3

Independientemente de la configuración que tenga para C3P0 (a través de la hibernación) es posible que tenga una restricción impuesta por MySQL.Tenga en cuenta que, de forma predeterminada, la cantidad máxima de conexiones permitidas por MySQL es de 100. Así que incluso si le dices a C3P0 que reúna hasta 200, 500 o 1000 conexiones, esto será inalcanzable. Abra un shell de MySQL usando:

$ msql -u [user] -p 

y escriba lo siguiente para obtener el número máximo de conexiones permitidas:

$ show variables where Variable_name='max_connections'; 

Si el número devuelto es demasiado bajo para su aplicación, considere cambiarlo (editar su archivo my.cnf, generalmente ubicado dentro de/etc/mysql/en sistemas Linux).

0

También tuve este problema. La causa fue que el usuario no tiene las concesiones para el host, porque la entrada/etc/hosts se ha modificado.

0

También tuve este problema y lo resolví estableciendo la propiedad checkoutTimeout de C3P0 a 0 en lugar de un valor superior a 0.

De hecho, tenía un montón de subprocesos en espera para una conexión y después de 10 s , los mismos errores que los tuyos ocurrieron.

Véase el documento aquí: http://www.mchange.com/projects/c3p0/#checkoutTimeout

Cuestiones relacionadas