node.js, mongodb, redis, en la degradación del rendimiento de ubuntu en producción, RAM es libre, CPU 100%


11

Como sugiere el título de la pregunta, estoy teniendo dificultades para descubrir qué se puede mejorar en mi aplicación (o sintonizado en el sistema operativo, ubuntu) para lograr un rendimiento aceptable. Pero primero explicaré la arquitectura:

El servidor front-end es una máquina de 8 núcleos con 8 gigas de RAM que ejecuta Ubuntu 12.04. La aplicación está escrita completamente en javascript y se ejecuta en node.js v 0.8.22 (ya que algunos módulos parecen quejarse en las versiones más recientes del nodo). Uso nginx 1.4 para proxy del tráfico http desde el puerto 80 y 443 a 8 trabajadores de nodo que se gestionan y comenzó a usar la API de clúster de nodo Utilizo la última versión de socket.io 0.9.14 para manejar las conexiones websocket, en las que he habilitado solo websockets y xhr-polling como transportes disponibles. En esta máquina también ejecuto una instancia de Redis (2.2)

Almaceno datos persistentes (como usuarios y puntajes) en un segundo servidor en mongodb (3.6) con 4 gigs de RAM y 2 núcleos.

La aplicación está en producción desde hace unos meses (se ha estado ejecutando en una sola caja hasta hace unas semanas) y está siendo utilizada por alrededor de 18k usuarios por día. Siempre ha funcionado muy bien aparte de un problema principal: la degradación del rendimiento. Con el uso, la cantidad de CPU utilizada por cada proceso aumenta hasta que estatiza al trabajador (que ya no atenderá solicitudes). Lo he resuelto temporalmente comprobando la CPU en uso por cada trabajador cada minuto y reiniciando si alcanza el 98%. Entonces, el problema aquí es principalmente CPU, y no RAM. La RAM ya no es un problema ya que he actualizado a socket.io 0.9.14 (la versión anterior tenía pérdidas de memoria), así que dudo que sea un problema de pérdida de memoria, especialmente porque ahora es la CPU que crece bastante rápido ( ¡Tengo que reiniciar a cada trabajador alrededor de 10-12 veces al día!). La RAM en uso también crece para ser honesto, pero muy lentamente, 1 concierto cada 2-3 días de uso, y lo extraño es que no se libera incluso cuando reinicio por completo toda la aplicación. ¡Solo se lanza si reinicio el servidor! esto realmente no puedo entender ...

Ahora descubrí nodefly, que es increíble, así que finalmente puedo ver lo que está sucediendo en mi servidor de producción, y estoy recopilando datos desde hace un par de días. Si alguien quiere ver los cuadros, puedo darle acceso, ¡pero básicamente puedo ver que tengo entre 80 y 200 conexiones simultáneas! Esperaba que node.js manejara miles, no cientos de solicitudes. Además, el tiempo de respuesta promedio para el tráfico http flota entre 500 y 1500 milisegundos, lo que creo que es realmente mucho. Además, en este mismo momento con 1300 usuarios en línea, esta es la salida de "ss -s":

Total: 5013 (kernel 5533)
TCP:   8047 (estab 4788, closed 3097, orphaned 139, synrecv 0, timewait 3097/0), ports 0

Transport Total     IP        IPv6
*         5533      -         -
RAW       0         0         0
UDP       0         0         0
TCP       4950      4948      2
INET      4950      4948      2
FRAG      0         0         0

lo que muestra que tengo muchas conexiones cerradas en el tiempo de espera. He aumentado los archivos abiertos máximos a 999999, aquí está la salida de ulimit -a:

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 63724
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 999999
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 63724
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

Así que pensé que el problema podría estar en el tráfico http que, por alguna razón, satura los puertos / sockets disponibles (?), Pero una cosa no tiene sentido para mí: por qué cuando reinicio a los trabajadores y todos los clientes se vuelven a conectar en unos segundos, la carga en la CPU del trabajador baja al 1% y es capaz de atender las solicitudes correctamente hasta que se satura después de aproximadamente 1 hora (en la hora pico)?

Soy principalmente un programador de JavaScript, no un administrador del sistema, por lo que no sé cuánta carga debería esperar manejar con mis servidores, pero seguramente no está funcionando como debería. De lo contrario, la aplicación es estable y este último problema me impide enviar las versiones móviles de la aplicación que están listas, ya que obviamente traerán más carga y eventualmente colapsarán todo.

Espero que haya algo obvio que estoy haciendo mal, y alguien ayudará a detectarlo ... no dude en pedirme más información, y lo siento por la longitud de la pregunta, pero fue necesario, creo ... ¡gracias por adelantado!


¿Hay alguna forma de obtener algo como volcado de subprocesos desde node.js? Probablemente hay algunos hilos en un bucle infinito. Además, ¿qué es realmente usar CPU? ¿Qué ves topcuando el uso de la CPU está cerca del 100%?
rvs

cpu es usado completamente por nodejs, cuando ejecuto arriba veo que los procesos de nodo toman toda la cpu. No estoy seguro de cómo puedo generar un volcado de subprocesos desde el nodo para ser sincero ...
Franjanko

Otra cosa a destacar es que la mayor parte del tiempo de la CPU parece ir al sistema, no al tiempo del usuario
Franjanko

¿Al menos alguien sabe cuántas conexiones concurrentes debería poder manejar con los servidores que tengo instalados? en este momento soporto 200 conexiones concurrentes max. Esto me ayudará a estimar qué tan lejos estoy de una configuración óptima ... gracias.
Franjanko

Respuestas:


10

Después de unos días de intensas pruebas y errores, me alegra poder decir que he entendido dónde estaba el cuello de botella, y lo publicaré aquí para que otras personas puedan beneficiarse de mis hallazgos.

El problema radica en las conexiones pub / sub que estaba usando con socket.io, y en particular en el RedisStore utilizado por socket.io para manejar la comunicación entre procesos de instancias de socket.

Después de darme cuenta de que podía implementar fácilmente mi propia versión de pub / sub usando redis, decidí probarlo y eliminé redisStore de socket.io, dejándolo con el almacén de memoria predeterminado (no necesito transmitir a todos los clientes conectados pero solo entre 2 usuarios diferentes conectados posiblemente en procesos diferentes)

Inicialmente, declaró que solo 2 conexiones redis globales x proceso para manejar el pub / sub en cada cliente conectado, y la aplicación estaba usando menos recursos pero todavía me estaba afectando el crecimiento constante del uso de la CPU, por lo que no había cambiado mucho. Pero luego decidí intentar crear 2 nuevas conexiones a redis para que cada cliente maneje su pub / sub solo en sus sesiones, luego cerré las conexiones una vez que el usuario se desconectó. Luego, después de un día de uso en producción, las CPU todavía estaban en 0-5% ... ¡bingo! no se reinicia el proceso, no hay errores, con el rendimiento que esperaba tener. Ahora puedo decir que node.js es genial y estoy feliz de haberlo elegido para construir esta aplicación.

Afortunadamente, redis ha sido diseñado para manejar muchas conexiones concurrentes (de manera diferente por mongo) y, de forma predeterminada, está configurado en 10k, lo que deja espacio para alrededor de 5k usuarios concurrentes, en una sola instancia de redis, lo cual es suficiente por el momento para mí, pero yo ' He leído que se puede impulsar hasta 64k conexiones simultáneas, por lo que esta arquitectura debería ser lo suficientemente sólida, creo.

En este punto, estaba pensando implementar algún tipo de grupo de conexiones para redistribuir, para optimizarlo un poco más, pero no estoy seguro de si eso no hará que los eventos pub / sub se acumulen nuevamente en las conexiones, a menos que cada uno de ellos se destruye y se recrea cada vez, para limpiarlos.

De todos modos, gracias por sus respuestas, y tendré curiosidad por saber qué piensan y si tienen alguna otra sugerencia.

Salud.


2
Tengo lo que parece ser el mismo problema en mi aplicación de producción, también nuevo en la función de administrador del servidor. Sigo lo que hiciste en concepto, pero tengo algunas preguntas sobre cómo hacerlo, ¿tal vez podrías proporcionar un enlace a algún recurso en tu respuesta aceptada? ¿O simplemente proporcionar más información? En particular sobre "Pero luego decidí intentar crear 2 nuevas conexiones para redis para que cada cliente maneje su pub / sub solo en sus sesiones, luego cerré las conexiones una vez que el usuario se desconectó".
toblerpwn

2

¿Tienes algún código fuente para volcar? ¿Pueden ser conexiones a la base de datos no cerradas? Procesos en espera de conexiones HTTP que nunca se cierran.

¿Puedes publicar algunos registros?

Haz un ps -ef y asegúrate de que nada sigue ejecutándose. He visto procesos web que dejan zombis que no morirán hasta que mates a -9. A veces, el apagado no funciona o no funciona por completo y esos subprocesos o procesos contendrán RAM y, a veces, CPU.

Podría ser un bucle infinito en algún lugar del código o un proceso bloqueado que se mantiene encima de una conexión db.

¿Qué módulos NPM están usando? ¿Son todos los últimos?

¿Estás atrapando excepciones? Ver: http://geoff.greer.fm/2012/06/10/nodejs-dealing-with-errors/ Ver: /programming/10122245/capture-node-js-crash-reason

Consejos generales:

http://clock.co.uk/tech-blogs/preventing-http-raise-hangup-error-on-destroyed-socket-write-from-crashing-your-nodejs-server

http://blog.nodejitsu.com/keep-a-nodejs-server-up-with-forever

http://hectorcorrea.com/blog/running-a-node-js-web-site-in-production-a-beginners-guide

/programming/1911015/how-to-debug-node-js-applications

https://github.com/dannycoates/node-inspector

http://elegantcode.com/2011/01/14/taking-baby-steps-with-node-js-debugging-with-node-inspector/


1

No es una respuesta per se, ya que su pregunta es más una historia que una pregunta puntual de una respuesta.

Solo para decir que construí con éxito un servidor node.js con socket.io que maneja más de 1 millón de conexiones persistentes con un promedio de carga de mensajes de 700 Bytes.

La tarjeta de interfaz de red a 1 Gbps estaba saturada al principio, y estaba viendo MUCHA espera de E / S por publicar eventos para todos los clientes.

La eliminación de nginx de la función de proxy también había devuelto una memoria valiosa, porque alcanzar un millón de conexiones persistentes con solo UN servidor, es un trabajo difícil de ajustar las configuraciones, las aplicaciones y ajustar los parámetros del sistema operativo. Tenga en cuenta que solo es factible con mucha RAM (alrededor de 1M de conexiones websockets consume aproximadamente 16GB de RAM, con node.js, creo que usar sock.js sería ideal para un bajo consumo de memoria, pero por ahora, socket.io consume tanto).

Este enlace fue mi punto de partida para alcanzar ese volumen de conexiones con el nodo. Además de ser una aplicación Erlang, todo el ajuste del sistema operativo es prácticamente independiente de la aplicación y debe ser utilizado por cualquier persona que tenga como objetivo muchas conexiones persistentes (websockets o encuestas largas).

HTH

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.