Apache + Tomcat tiene problemas para comunicarse. Mensajes de error poco claros. Derribar sitios web alojados bajo Tomcat

22

Configuración:
Fedora 8
Apache 2.2.8
Tomcat 5.5.8
Apache está reenviando solicitudes utilizando AJP.

Problema:
después de un cierto período de tiempo (no constante en absoluto, puede ser entre una hora o dos, o uno o más días) Tomcat bajará. O deja de responder o pone el genérico 'Servicio temporalmente no disponible'.

Diagnóstico:
hay dos servidores con la misma configuración. Uno alberga un sitio web de mayor tráfico (varias solicitudes por segundo), el otro uno de bajo tráfico (un puñado de solicitudes cada pocos minutos). Ambos sitios web son bases de código completamente diferentes, pero presentan problemas similares.

En el primer servidor, cuando se produce el problema, todos los subprocesos comienzan a tomarse lentamente hasta alcanzar el límite (MaxThreads 200). En ese momento, el servidor ya no responde (y aparece la página de servicio no disponible después de un largo período de tiempo).

En el segundo servidor, cuando se produce el problema, las solicitudes tardan mucho tiempo y cuando se realizan, todo lo que ve es la página de servicio no disponible.

Aparte de la mención del problema MaxThreads, los registros de Tomcat no indican ningún problema específico que pueda estar causando esto.

Sin embargo, en los registros de Apache estamos viendo mensajes aleatorios que se refieren a AJP. Aquí hay una muestra de mensaje aleatorio que vemos (sin un orden específico):

[error] (70007)The timeout specified has expired: ajp_ilink_receive() can't receive header
[error] (104)Connection reset by peer: ajp_ilink_receive() can't receive header
[error] proxy: AJP: disabled connection for (localhost)
[error] ajp_read_header: ajp_ilink_receive failed
[error] (120006)APR does not understand this error code: proxy: read response failed from 127.0.0.1:8009 (localhost)
[error] ap_proxy_connect_backend disabling worker for (localhost)

La otra cosa extraña que hemos notado en el servidor de mayor tráfico es que justo antes de que el problema comience a suceder, las consultas a la base de datos tardan mucho más que antes (2000-5000 ms en comparación con normalmente 5-50ms). Esto solo dura de 2 a 4 segundos antes de que aparezca el mensaje MaxThreads. Supongo que esto es el resultado del servidor que de repente trata con demasiados datos / tráfico / hilos.

Información básica:
estos dos servidores han estado funcionando sin problemas durante bastante tiempo. En realidad, los sistemas se configuraron utilizando dos NIC durante ese tiempo. Separaron el tráfico interno y externo. Después de una actualización de red, cambiamos estos servidores a NIC individuales (esto nos lo recomendaron por razones de seguridad / simplicidad). Después de ese cambio, los servidores comenzaron a tener estos problemas.

Resolución:
la solución obvia sería volver a una configuración de dos NIC. Los problemas con eso son que causaría algunas complicaciones con la configuración de la red, y parece ignorar el problema. Preferiríamos probar y ejecutarlo en una sola configuración de NIC.

Buscar en Google los diversos mensajes de error no proporcionó nada útil (ya sea soluciones antiguas o no relacionadas con nuestro problema).

Intentamos ajustar los diversos tiempos de espera, pero eso solo hizo que el servidor se ejecutara un poco más antes de morir.

No estamos seguros de dónde buscar para diagnosticar más el problema. Todavía nos estamos aferrando a los popotes sobre cuál podría ser el problema:

1) La configuración con AJP y Tomcat es incorrecta u obsoleta (es decir, ¿errores conocidos?)
2) La configuración de la red (dos NIC versus una NIC) está causando confusión o problemas de rendimiento.
3) Los sitios web en sí (no hay código común, no se utilizan plataformas, solo código Java básico con servlets y JSP)

Actualización 1:
Siguiendo los consejos útiles de David Pashley, hice un volcado de seguimiento / subproceso de pila durante el problema. Lo que encontré fue que los 200 hilos estaban en uno de los siguientes estados:

"TP-Processor200" daemon prio=1 tid=0x73a4dbf0 nid=0x70dd waiting for monitor entry [0x6d3ef000..0x6d3efeb0]
at  oracle.jdbc.pool.OracleConnectionCacheImpl.getActiveSize(OracleConnectionCacheImpl.java:988)
- waiting to lock <0x7e3455a0> (a oracle.jdbc.pool.OracleConnectionCacheImpl)
[further stack trace removed for brevity]

"TP-Processor3" daemon prio=1 tid=0x08f142a8 nid=0x652a waiting for monitor entry [0x75c7d000..0x75c7ddb0]
at oracle.jdbc.pool.OracleConnectionCacheImpl.getConnection(OracleConnectionCacheImpl.java:268)
- waiting to lock <0x7e3455a0> (a oracle.jdbc.pool.OracleConnectionCacheImpl)
[further stack trace removed for brevity]

Curiosamente, solo uno de los 200 hilos estaba en este estado:

"TP-Processor2" daemon prio=1 tid=0x08f135a8 nid=0x6529 runnable [0x75cfe000..0x75cfef30]
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at oracle.net.ns.Packet.receive(Unknown Source)
at oracle.net.ns.DataPacket.receive(Unknown Source)
at oracle.net.ns.NetInputStream.getNextPacket(Unknown Source)
at oracle.net.ns.NetInputStream.read(Unknown Source)
at oracle.net.ns.NetInputStream.read(Unknown Source)
at oracle.net.ns.NetInputStream.read(Unknown Source)
[further stack trace removed for brevity]

Es posible que el controlador de Oracle en este hilo esté obligando a todos los otros hilos a esperar a que se complete. Por alguna razón, debe estar atascado en este estado de lectura (el servidor nunca se recupera solo, requiere un reinicio).

Esto sugiere que debe estar relacionado con la red entre el servidor y la base de datos, o con la base de datos misma. Continuamos los esfuerzos de diagnóstico, pero cualquier consejo sería útil.

Jordy Boom
fuente
En primer lugar, esta es una pregunta increíblemente escrita. Fantástico trabajo en los detalles! En segundo lugar, ¿está utilizando proxy_ajp o mod_jk para conectar los servidores Apache y Tomcat?
Ophidian
Estoy usando proxy_ajp para conectar los dos.
Jordy Boom
Haga pruebas de estrés con asedio, joedog.org/siege-home .
paalfe

Respuestas:

9

Resulta que esta versión (clases 12 - bastante antigua) del controlador Oracle tenía varios errores que causaban un punto muerto (como se ve en el estado TP-Processor2 citado anteriormente). No se activó hasta que cambiamos al nuevo entorno. La actualización a la última versión (ojdbc14) ha resuelto el problema en el servidor primario.

Jordy Boom
fuente
Esto me llevó a mi solución correcta: tenía un bloqueo en una fila DB ... y nunca tuve ninguna excepción en el servidor de aplicaciones
cljk
6

Según la descripción, sugeriría que el problema puede deberse a que las consultas de la base de datos tardan demasiado. Si las consultas tardan más, la solicitud tardará más y, por lo tanto, tendrá más de ellas ejecutándose a la vez. Como estás viendo, te estás quedando sin hilos de tomcat. Cuando resuelva el problema con la base de datos, debería estar bien.

  • Obtenga un seguimiento de la pila, ya sea usando jstack o usando kill -3 $ process_id. Vea lo que hacen sus hilos cuando muere. Si todos están esperando en la base de datos, ese es un buen indicador de mi teoría. Todos podrían estar esperando en alguna cerradura.
  • Instale LambdaProbe. Es invaluable para descubrir lo que está haciendo su gato.
  • Actualiza tu tomcat. 5.5.8 es increíblemente viejo. Creo que ahora están en 5.5.27.
David Pashley
fuente
David, actualicé la pregunta (ver Actualización 1) con nuevos hallazgos basados ​​en tu sugerencia de volcado / seguimiento de pila.
Jordy Boom
Sugeriría que su grupo de conexiones de base de datos sea demasiado pequeño en comparación con su valor de conexión máxima de tomcat. Parece que la mayoría de los subprocesos esperan obtener una conexión de base de datos.
David Pashley
La única razón por la que hay tantos subprocesos es porque los subprocesos que se usan normalmente se quedan esperando que ese subproceso intente leer desde el socket. El número de conexiones de base de datos que se utilizan en cualquier momento oscila entre 1 y 3. Nunca hay una necesidad de más que esa cantidad.
Jordy Boom
5

Agregue connectionTimeout y keepAliveTimeout a su conector AJP que se encuentra en /etc/tomcat7/server.xml.

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" 
           connectionTimeout="10000" keepAliveTimeout="10000" />

Información sobre el conector AJP en https://tomcat.apache.org/tomcat-7.0-doc/config/ajp.html

  • connectionTimeout = El número de milisegundos que este conector esperará, después de aceptar una conexión, para que se presente la línea URI de solicitud. El valor predeterminado para los conectores de protocolo AJP es -1 (es decir, infinito).

  • keepAliveTimeout = El número de milisegundos que este conector esperará otra solicitud de AJP antes de cerrar la conexión. El valor predeterminado es usar el valor que se ha establecido para el atributo connectionTimeout.

Si los valores de connectionTimeout y keepAliveTimeout no están definidos, las conexiones AJP se mantendrán con vida por infinito. Causando muchos subprocesos, los subprocesos máximos predeterminados son 200.

Recomiendo instalar psi-probe, un administrador y monitor avanzado para Apache Tomcat, bifurcado de Lambda Probe. https://code.google.com/p/psi-probe/

paalfe
fuente
4

Debido a la forma en que funciona AJP, las conexiones persistentes entre apache (usando mod_proxy_ajp o mod_jk) solo pueden ser cerradas de manera segura por el cliente . En este caso, el cliente es el trabajador de Apache que se abre y luego mantiene una conexión con Tomcat durante toda la vida del proceso del trabajador .

Debido a este comportamiento, no puede tener más trabajadores apache que hilos de trabajo tomcat. Si lo hace, los trabajadores http adicionales no podrán conectarse a Tomcat (ya que la cola de aceptación está llena) y marcará su backend como ABAJO.

Dave Cheney
fuente
1
Perdón por el comentario después de todos estos años, pero ¿no podría garantizarse esto configurando el indicador máximo dentro de la configuración de ProxyPass con el número de MaxThreads del contenedor de servlet?
Horst Gutmann
2

He tenido mejores resultados con mod_proxy en lugar de mod_ajp en términos de estabilidad, así que prueba esa solución. No es invasivo: en el mejor de los casos resolverá el problema y en el peor de los casos descartará mod_ajp.

Aparte de eso, parece que sus Tomcats dejan de responder y todos los hilos de solicitud están atados. Haga que su equipo de desarrollo analice lo que está sucediendo: será útil realizar un volcado de subprocesos y entregárselos.

Robert Munteanu
fuente
Tenía la impresión de que mod_proxy tiene algunos problemas de escalabilidad a pesar de ser más fácil de conectar. Parece que la fundación Apache recomienda mod_jk ( wiki.apache.org/tomcat/FAQ/Connectors#Q2 )
Ophidian
No proporciona una sesión adhesiva, es cierto. Pero aparte de eso, nunca he tenido problemas con eso.
Robert Munteanu
1

Lo primero en lo que pienso cuando escucho que un servidor se ejecuta por un tiempo, de repente se ralentiza y luego comienza a tener fallas en el servicio, es que se está quedando sin RAM e intercambiando. No tengo claro si las fallas de AJP que estás viendo podrían ser consecuencia de tiempos de espera, pero no parece completamente irrazonable; Sin embargo, no veo ninguna forma obvia de conectarse a la NIC. En cualquier caso, le recomiendo que obtenga una imagen de lo que sucede con el uso de su memoria cuando se producen estos eventos.

Si se está quedando sin RAM, es posible que deba rechazar su Apache MaxClientsy aumentar su ListenBacklog.

Por cierto, gracias por hacer su pregunta tan bien organizada y completa.

caos
fuente
Cuando observo 'top' mientras esto sucede, el uso de memoria sigue siendo bastante constante. Al menos no hay picos. Solo hay un breve momento de alto uso de CPU.
Jordy Boom
1

Tuve errores de registro similares en el entorno Redhat con proxy_ajp y Tomcat. Resuelto actualizando el paquete httpd:

yum update httpd

desde:

  • httpd-devel-2.2.3-43.el5_5.3.x86_64
  • httpd-2.2.3-43.el5_5.3.x86_64

a:

  • httpd-2.2.3-45.el5_6.3.x86_64
  • httpd-devel-2.2.3-45.el5_6.3.x86_64

Luego reinició Apache, seguido de reiniciar Tomcat.

¡Eso me lo arregló!

Bajo
fuente