node.js serveride: funciona con errores. Parte 1

Buenas tardes

Este artículo está dirigido a desarrolladores que estén familiarizados con node.js.

Recientemente estaba preparando material sobre hechos que son útiles para los desarrolladores en node.js en nuestra oficina. Los proyectos en los que estamos trabajando son servicios API que utilizan el módulo express node.js como servidor web. El material se basa en casos reales en los que el código funcionó incorrectamente o la lógica en él se ocultó cuidadosamente, o provocó errores durante la expansión. Con base en este material, se realizó un taller de desarrollo del personal.

Entonces, decidí compartir. Hasta ahora, solo la primera parte es aproximadamente el 30%. Si está interesado, se continuará!

Traté de proporcionar una oportunidad para una rápida familiarización, así que escondí los ejemplos, razonamientos y comentarios en los spoilers. Si las declaraciones son obvias, puede omitir el "agua". Aunque nuestro "rastrillo" en los spoilers también puede ser interesante.

Un colega durante el seminario me hizo una pregunta, por qué hablar sobre eso, si todo ya está en esta o aquella documentación. Mi respuesta fue la siguiente. A pesar de que el mensaje es verdadero, todo está realmente en la documentación, aún cometemos errores molestos relacionados con la incomprensión o la ignorancia de las cosas básicas.

¡Empecemos!

Máquina virtual Node.js



Roscado individual



A diferencia de javavm, nodejs-vm es de subproceso único ** .



Fuente

mas detalles
Al mismo tiempo, hay un grupo de subprocesos auxiliares que utiliza la máquina virtual, por ejemplo, para organizar E / S. Pero todo el código de usuario se ejecuta en un solo hilo "principal".

Esto simplifica enormemente la vida, ya que no hay competencia. La ejecución del código no se puede interrumpir en un lugar arbitrario y continuar en otro. El código simplemente se ejecuta hasta que sea necesario esperar algo, por ejemplo, la disponibilidad de datos al leer desde un archivo. Mientras espera, se puede ejecutar otro controlador, ya sea hasta que termine de funcionar, o hasta que también comience a esperar algo.

Es decir, si hay una estructura de datos interna, ¡no necesita preocuparse por sincronizar el acceso a ella!

¿Qué hacer si el hilo "principal" no tiene tiempo para procesar los datos?

El escalado se realiza iniciando otro proceso node.js o, si los recursos del servidor están llegando a su fin, iniciando otro servidor.

consecuencias y nuestro "rastrillo"
Aquí todo está claro también. Siempre debe estar preparado para el hecho de que puede haber (y muy probablemente habrá) más de un proceso de node.js. Y a veces también puede haber varios servidores.

El "rastrillo" que estaba oculto se encuentra en nuestro código


Las líneas paralelas se cruzan en el infinito. Es imposible de probar, pero lo vi.
Jean Effel, "La novela de Adán y Eva".
Se hizo un intento para garantizar la unicidad de las instancias de entidad en la base de datos exclusivamente por la aplicación. En general, esto y aislado del contexto no se ve muy bien, pero en esta situación aún más. Sin contratar un servicio de terceros, esta tarea me parece que no tiene solución.

El colega que estaba involucrado en esto, realmente, realmente quería implementar esto sin involucrar a la base de datos real. Al final, después de algunos "enfoques para el proyectil", se realizó ... al involucrar a SharePoint.


** Multithreading o "si realmente quieres"



A partir de la versión 10.5.0, node.js tiene soporte experimental para subprocesos múltiples.


Fuente

Pero el paradigma sigue siendo el mismo.
  • Cada nuevo flujo de trabajo crea su propia instancia aislada del entorno de la máquina virtual node.js.
  • Los flujos de trabajo carecen de datos mutables comunes. (Hay un par de reservas, pero básicamente la declaración es justa).
  • La comunicación se realiza mediante mensajes y SharedArrayBuffer.

Por lo tanto, el código anterior seguirá funcionando cuando se utilicen flujos de trabajo.
Lee más aquí.


Ciclo de vida de la aplicación



El corazón de nodejs-vm es el bucle de eventos. Cuando se debe suspender la ejecución del código o parece que el código ha terminado, el control pasa a él.

Texto oculto
El bucle de eventos verifica si (oh) se han producido los eventos para los que registramos los controladores. Si algo sucede, se llamará a los controladores. Y si no, se verificará si hay "generadores" de eventos para los que tenemos controladores registrados. Una conexión tcp abierta o un temporizador pueden ser tales generadores. Si no se pueden encontrar, el programa se cierra. De lo contrario, se espera uno de estos eventos, se llaman controladores y todo se repite.

La consecuencia de este comportamiento es el hecho de que cuando el código parece haber terminado, la salida de nodejs-vm no ocurre, por ejemplo, porque registramos un controlador de temporizador, que debería llamarse después de un tiempo.

Esto se muestra en el siguiente ejemplo.

console.log('registering timer callbacks'); setTimeout( function() { console.log('Timer Event 1'); }, 1000); console.log('Is it the end?'); 


resultado:
 registering timer callbacks Is it the end? Timer Event 1 


Lee más aquí.

Otro "rastrillo" en nuestro código



¡Todos pueden manejar el estado!
El signo de si el usuario es administrador se almacenó en una variable global. Esta variable se inicializó en falso al comienzo del programa. Más tarde, cuando el administrador se registró, esta variable se estableció en verdadero.

Como resultado, si el administrador visitó el sistema, cualquier usuario que accedió a esta instancia del servicio se percibió como administrador.

Me costó un poco de esfuerzo mostrarle a mi colega que había un error en la lógica. Un colega estaba seguro de que por cada solicitud http se crea un entorno completamente nuevo.


package.json: campos que vale la pena completar



package.json es el archivo de descripción de nuestro paquete. En este contexto, se trata de nuestra aplicación y no de dependencias. Los campos y explicaciones que se enumeran a continuación explican por qué vale la pena completarlos de todos modos.

Texto oculto

nombre


Hasta que publiquemos el paquete en el repositorio, el campo también se puede puntuar. La pregunta es que este campo es conveniente para nombrar el archivo de instalación o, por ejemplo, para mostrar el nombre del producto en su página web. En general, "¿cómo se llama un yate, .."

version


La idea principal es no olvidar aumentar el número de versión mientras se expande la funcionalidad, se corrigen errores, ... Desafortunadamente, en nuestra oficina aún puede encontrar productos con la versión sin cambios 0.0.0. Y luego adivina qué tipo de funcionalidad funciona para el cliente ...

principal


Este campo indica qué archivo se iniciará cuando se inicie nuestra aplicación (`npm start`). Si el paquete se usa como una dependencia, entonces qué archivo se importará al usar nuestro módulo por otra aplicación. El directorio actual es el directorio donde se encuentra el archivo `package.json`.

Y también, si, por ejemplo, usamos vscode , el archivo especificado en este campo se iniciará cuando se llame al depurador o cuando se ejecute el comando "ejecutar".

La extensión ".js" puede omitirse. Es más bien una consecuencia de todos los casos de uso posibles, por lo que no se detalla directamente en la documentación.

motores


Este campo contiene la tupla: {"nodo": versión , "npm": versión , ...}.

Conozco los campos "nodo" y "npm". Determinan las versiones de node.js y npm necesarias para que nuestra aplicación funcione. Las versiones se comprueban ejecutando el comando npm install.

Se admite la sintaxis estándar para determinar la versión de los paquetes de dependencia: sin un prefijo (versión única), el prefijo "~" (los dos primeros números de la versión deben coincidir) y el prefijo "^" (solo el primer número de la versión debe coincidir). Si hay un prefijo, la versión debe ser mayor o igual que la especificada en este campo. Solo una lista de versiones; indicación explícita más, menos, ... etc. También funciona.

Descargo de responsabilidad "Npm install" verifica las versiones especificadas en los "motores" solo si el modo "motor estricto" está habilitado. Lo incluimos para cada proyecto, agregando el archivo .npmrc con la línea: "engine-strictly = true". Érase una vez, "npm install" hizo esta verificación por defecto.

Algunos contenedores, al menos en la documentación, escriben que las versiones adecuadas se utilizarán por defecto. En este caso, estamos hablando de Azure.

Un ejemplo:
 "engines": { "node": "~8.11", // require node version 8.11.* starting from 8.11.0 "npm": "^6.0.1" // require npm version 6.* starting from 6.0.1 }, 


"rastrillo" regular



¡Y el rey está desnudo!

Se acordó repetidamente con el cliente que la versión requerida de `node.js` debería ser al menos 8. Cuando se entregaron las versiones iniciales de la aplicación, todo funcionó. "Un día" después de la entrega de la nueva versión en el cliente, la aplicación dejó de ejecutarse. Todo funcionó en nuestras pruebas.

El problema era que en esta versión comenzamos a usar funcionalidades que solo eran compatibles con la versión 8 node.js. El campo "motores" no estaba lleno, por lo que nadie había notado antes que el cliente tenía una versión antigua de node.js. (Servicios web de Azure predeterminados).

guiones


El campo contiene una tupla de la forma: {"script1": script1 , "script2": script2 , ...}.

Hay scripts estándar que se ejecutan en una situación dada. Por ejemplo, el script "instalar" se ejecutará después de ejecutar "npm install". Es muy conveniente, por ejemplo, verificar la disponibilidad de los programas necesarios para que la aplicación funcione. O, por ejemplo, comprimir todos los archivos estáticos disponibles a través de nuestro servicio web para que no tengan que comprimirse sobre la marcha.

En este caso, no puede limitarse solo a los nombres estándar. Para ejecutar un script arbitrario, debe ejecutar "npm run script-name ".

Es conveniente recopilar todos los scripts usados ​​en un solo lugar.

Un ejemplo:
  "scripts": { "install": "node scripts/install-extras", "start": "node src/well/hidden/main/server extra_param_1 extra_param_2", "another-script": "node scripts/another-script" } 


PD La extensión ".js" se puede omitir en la mayoría de los casos.


package-lock.json: ayuda a instalar versiones específicas de dependencias, no las "últimas"



Texto oculto
¿Git o no git? ..


Este archivo apareció en npm relativamente recientemente. Su propósito es organizar la repetibilidad de la asamblea.

y un "rastrillo" más


¡Pero no cambié nada en mi programa! Ayer ella trabajó!


En una máquina similar, la aplicación funcionó muy bien. En otra computadora en un entorno idéntico, en una aplicación colocada desde git a un nuevo directorio, después de ejecutar 'npm install', aparecieron errores hasta ahora 'npm start'.

El problema fue causado por el hecho de que faltaba el archivo 'package-lock.json' en el repositorio de git. Por lo tanto, durante la instalación de paquetes, todas las dependencias del segundo o más niveles (naturalmente, no escritas en package.json) se instalaron lo más fresco posible. En la computadora de un colega, todo estaba bien. Se seleccionó un conjunto incompatible de versiones en la computadora probada.

package-lock.json - a git!



Volviendo de la digresión. El archivo 'package-lock.json' contiene una lista de todos los módulos instalados localmente para nuestra aplicación. La presencia de este archivo le permite recrear un conjunto de versiones de módulos uno a uno.

Resumen: ¡no olvide poner git e incluirlo en el archivo de entrega (instalación) de la aplicación!

Útil: si falta el archivo 'package-lock.json', pero hay un directorio 'node_modules' con todos los módulos necesarios, se puede recrear el archivo 'package-lock.json':
 npm shrinkwrap rename npm-shrinkwrap.json package-lock.json 



Puedes poner fin a esto por ahora. La información no incluida es la técnica de simplificación de código utilizada por nuestro equipo.

Si se detectan errores, intentaré solucionarlos rápidamente.

Source: https://habr.com/ru/post/es430972/


All Articles