La aplicación web Java en Tomcat se congela periódicamente
Mi aplicación web Java que ejecuta Tomcat (7.0.28) deja de responder periódicamente. Espero algunas sugerencias de posibles culpables (¿sincronización?), así como quizás algunas herramientas recomendadas para recopilar más información sobre lo que ocurre durante un bloqueo. Algunos datos que he acumulado:
Cuando la aplicación web se congela, Tomcat continúa alimentando subprocesos de solicitud en la aplicación, pero la aplicación no los libera. El grupo de subprocesos se llena hasta el máximo (actualmente 250) y luego las solicitudes posteriores fallan inmediatamente. Durante el funcionamiento normal, nunca hay más de 2 o 3 subprocesos activos.
No se registran errores ni excepciones de ningún tipo en ninguno de nuestros registros de aplicaciones web o de Tomcat cuando se produce el problema.
Hacer una "Detención" y luego un "Inicio" en nuestra aplicación a través de la aplicación web de administración de Tomcat soluciona inmediatamente este problema (hasta hoy).
Últimamente la frecuencia ha sido de dos o tres veces al día, aunque hoy fue mucho peor, probablemente 20 veces, y en ocasiones no vuelve a la vida inmediatamente.
El problema ocurre solo durante el horario comercial.
El problema no ocurre en nuestro sistema de preparación.
Cuando ocurre el problema, el uso del procesador y la memoria en el servidor permanece estable (y bastante bajo). Tomcat informa mucha memoria libre.
Tomcat sigue respondiendo cuando ocurre el problema. La aplicación web de administración funciona perfectamente y Tomcat continúa enviando solicitudes a nuestra aplicación hasta que se llenan todos los subprocesos del grupo.
Nuestro servidor de base de datos sigue respondiendo cuando ocurre el problema. Usamos Spring Framework para el acceso e inyección de datos.
El problema generalmente ocurre cuando el uso es alto, pero nunca hay un pico inusualmente alto en el uso.
Historia del problema: algo similar ocurrió hace aproximadamente un año y medio. Después de muchos cambios de código y configuración del servidor, el problema desapareció hasta hace aproximadamente un mes. En las últimas semanas ha ocurrido con mucha más frecuencia, un promedio de 2 o 3 veces al día, a veces varias veces seguidas.
Hoy identifiqué un código de servidor que puede no haber sido seguro para subprocesos y lo solucioné, pero el problema continúa ocurriendo (aunque con menos frecuencia). ¿Es este el tipo de problema que puede causar el código no seguro para subprocesos?
ACTUALIZACIÓN: Con varias publicaciones que sugieren el agotamiento del grupo de conexiones de la base de datos, busqué un poco en esa dirección y encontré esta otra pregunta de Stackoverflow que explica casi todos los problemas que estoy experimentando.
Aparentemente, los valores predeterminados para las conexiones maxActive y maxIdle en la implementación BasicDataSource de Apache son 8 cada uno. Además, maxWait se establece en -1, por lo que cuando el grupo se agota y llega una nueva solicitud de conexión, esperará para siempre sin registrar ninguna especie de excepción. Todavía esperaré a que este problema vuelva a ocurrir y realizaré un volcado de jstack en la JVM para poder analizar esa información, pero parece que este es el problema. Lo único que no explica es por qué la aplicación a veces no se recupera de este problema. Supongo que las solicitudes se acumulan a veces y una vez que se atrasan, nunca podrán ponerse al día.
ACTUALIZACIÓN II: Ejecuté un jstack durante un bloqueo y encontré alrededor de 250 (máximo de subprocesos) de lo siguiente:
"http-nio-443-exec-294" daemon prio=10 tid=0x00002aaabd4ed800 nid=0x5a5d in Object.wait() [0x00000000579e2000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:485)
at org.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:1118)
- locked <0x0000000743116b30> (a org.apache.commons.pool.impl.GenericObjectPool$Latch)
at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:106)
at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
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)
Para mi ojo inexperto, esto parece bastante concluyente. Parece que el grupo de conexiones de la base de datos ha alcanzado su límite. Configuré un maxWait de tres segundos sin modificar maxActive y maxIdle solo para asegurarme de que comenzamos a ver excepciones registradas cuando el grupo se llena. Luego estableceré esos valores en algo apropiado y los monitorearé.
ACTUALIZACIÓN III: Después de configurar maxWait, comencé a ver esto en mis registros, como se esperaba:
org.apache.commons.dbcp.SQLNestedException: Cannot get a connection, pool error Timeout waiting for idle object
at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:114)
at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)
Configuré maxActive en -1 (infinito) y maxIdle en 10. Lo monitorearé por un tiempo, pero supongo que este es el final del problema.
Por experiencia, es posible que desee ver la implementación del grupo de conexiones de su base de datos . Podría ser que su base de datos tenga mucha capacidad, pero el grupo de conexiones de su aplicación esté limitado a una pequeña cantidad de conexiones. No recuerdo los detalles, pero creo recordar haber tenido un problema similar, que fue una de las razones por las que cambié a usar BoneCP , que descubrí que es muy rápido y confiable bajo pruebas de carga.
Después de probar la depuración sugerida a continuación, intente aumentar la cantidad de conexiones disponibles en el grupo y vea si eso tiene algún impacto.
Hoy identifiqué un código de servidor que puede no haber sido seguro para subprocesos y lo solucioné, pero el problema continúa ocurriendo (aunque con menos frecuencia). ¿Es este el tipo de problema que puede causar el código no seguro para subprocesos?
Depende de lo que quieras decir con seguro para subprocesos. Me parece que su aplicación está provocando que los subprocesos se bloqueen . Es posible que desee ejecutar su entorno de producción con la JVM configurada para permitir que se conecte un depurador y luego usar JVisualVM, JConsole u otra herramienta de creación de perfiles (YourKit es excelente en mi opinión) para echar un vistazo a qué subprocesos tiene y cuáles son. Estamos esperando.