node.js serverside - fonctionne sur les bugs. Partie 1

Bon après-midi

Cet article s'adresse aux développeurs familiers avec node.js.

Récemment, je préparais du matériel sur des faits qui sont utiles à connaître pour les développeurs sous node.js dans notre bureau. Les projets sur lesquels nous travaillons sont des services d'API qui utilisent le module express node.js comme serveur Web. Le matériel est basé sur des cas réels dans lesquels le code a fonctionné de manière incorrecte ou la logique qu'il contient a été soigneusement cachée, ou il a provoqué des erreurs lors de l'expansion. Sur la base de ce matériel, un atelier de développement du personnel a été organisé.

J'ai donc décidé de partager. Jusqu'à présent, seule la première partie représente environ 30%. Si vous êtes intéressé, sera poursuivi!

J'ai essayé de fournir une opportunité de familiarisation rapide, alors j'ai caché les exemples, le raisonnement et les commentaires dans les spoilers. Si les déclarations sont évidentes, vous pouvez sauter «l'eau». Bien que notre "râteau" dans les spoilers puisse aussi être intéressant.

Un collègue au cours du séminaire m'a posé une question, pourquoi en parler, si tout est déjà dans telle ou telle documentation. Ma réponse était la suivante. Malgré le fait que le message soit vrai, tout est vraiment dans la documentation, nous faisons toujours des erreurs gênantes liées à des malentendus ou à l'ignorance des choses de base.

Commençons!

Machine virtuelle Node.js



Filetage simple



Contrairement à javavm, nodejs-vm est monothread ** .



Source

plus de détails
Dans le même temps, il existe un pool de threads auxiliaires qui sont utilisés par la machine virtuelle elle-même, par exemple, pour organiser les E / S. Mais tout le code utilisateur est exécuté dans un seul thread, "principal".

Cela simplifie grandement la vie, car il n'y a pas de concurrence. L'exécution du code ne peut pas être interrompue dans un endroit arbitraire et poursuivie dans un autre. Le code est simplement exécuté jusqu'à ce qu'il soit nécessaire d'attendre quelque chose, par exemple, la préparation des données lors de la lecture d'un fichier. En attendant, un autre gestionnaire peut être exécuté, jusqu'à ce qu'il finisse de fonctionner, ou jusqu'à ce qu'il commence également à attendre quelque chose.

Autrement dit, s'il existe une structure de données interne, vous n'avez pas à vous soucier de la synchronisation de l'accès à celle-ci!

Que faire si le thread "principal" n'a pas le temps de traiter les données?

La mise à l'échelle se fait en démarrant un autre processus node.js ou, si les ressources du serveur arrivent à leur fin, en démarrant un autre serveur.

conséquences et notre "rake"
Ici aussi, tout est clair. Vous devez toujours être préparé au fait qu'il peut y avoir (et très probablement) plus d'un processus node.js. Et parfois, il peut également y avoir plusieurs serveurs.

Le "râteau" qui était caché se trouve dans notre code


Les lignes parallèles se coupent à l'infini. C'est impossible à prouver, mais j'ai vu.
Jean Effel, "Le roman d'Adam et Eve."
Une tentative a été faite pour garantir l'unicité des instances d'entité dans la base de données exclusivement par l'application. En général, cela et indépendamment du contexte ne semble pas très mal , mais dans cette situation encore plus. Sans engager un service tiers, cette tâche ne me semble pas avoir de solution.

Le collègue qui était engagé dans ce projet voulait vraiment mettre en œuvre cela sans impliquer la base de données réelle. Au final, après quelques "approches du projectile", il s'est réalisé ... en impliquant SharePoint.


** Multithreading ou "si vous voulez vraiment"



À partir de la version 10.5.0, node.js a un support expérimental pour le multithreading .


Source

Mais le paradigme reste le même
  • Chaque nouveau workflow crée sa propre instance isolée de l'environnement de la machine virtuelle node.js.
  • Les flux de travail manquent de données mutables communes. (Il y a quelques réserves, mais en gros, la déclaration est juste.)
  • La communication se fait à l'aide de messages et de SharedArrayBuffer.

Par conséquent, l'ancien code continuera de fonctionner lors de l'utilisation des workflows.
Lisez plus ici.


Cycle de vie de l'application



Le cœur de nodejs-vm est la boucle d'événements. Lorsque l'exécution du code doit être suspendue ou que le code semble avoir pris fin, le contrôle lui passe.

Texte masqué
La boucle d'événements vérifie si (oh) les événements pour lesquels nous avons enregistré les gestionnaires se sont produits. Si quelque chose se produit, les gestionnaires seront appelés. Sinon, il sera vérifié s'il existe des "générateurs" d'événements pour lesquels nous avons enregistré des gestionnaires. Une connexion TCP ouverte ou un minuteur peuvent être de tels générateurs. S'ils sont introuvables, le programme se ferme. Sinon, l'un de ces événements est attendu, des gestionnaires sont appelés et tout se répète.

La conséquence de ce comportement est le fait que lorsque le code semble être terminé, la sortie de nodejs-vm ne se produit pas, par exemple, car nous avons enregistré un gestionnaire de temporisation, qui devrait être appelé après un certain temps.

Ceci est illustré dans l'exemple suivant.

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


résultat:
 registering timer callbacks Is it the end? Timer Event 1 


Lisez plus ici.

Un autre "rake" dans notre code



Tout le monde peut gérer l'État!
Le signe indiquant si l'utilisateur est un administrateur a été stocké dans une variable globale. Cette variable a été initialisée à false au début du programme. Plus tard, lorsque l'administrateur s'est inscrit, cette variable a été définie sur true.

Par conséquent, si l'administrateur visitait le système, tout utilisateur ayant accédé à cette instance du service était perçu comme un administrateur.

Il m'a fallu quelques efforts pour montrer à mon collègue qu'il y avait une erreur de logique. Un collègue était sûr que pour chaque demande http, un environnement complètement nouveau était créé.


package.json - champs qui valent la peine d'être remplis



package.json est le fichier de description de notre package. Dans ce contexte, il s'agit de notre application et non des dépendances. Les champs et explications ci-dessous expliquent pourquoi il vaut la peine de les remplir tout de même.

Texte masqué

nom


Jusqu'à ce que nous publions le package dans le référentiel, le champ peut également être évalué. La question est que ce champ est pratique à utiliser pour nommer le fichier d'installation ou, par exemple, pour afficher le nom du produit sur sa page Web. En général, "comment appelez-vous un yacht, .."

version


L'idée principale est de ne pas oublier d'augmenter le numéro de version tout en étendant les fonctionnalités, en corrigeant les bugs, ... Malheureusement, dans notre bureau, vous pouvez toujours trouver des produits avec la version inchangée 0.0.0. Et ensuite, devinez quel type de fonctionnalité fonctionne pour le client ...

principal


Ce champ indique quel fichier sera lancé au démarrage de notre application (`npm start`). Si le package est utilisé comme dépendance, quel fichier sera importé lors de l'utilisation de notre module par une autre application. Le répertoire courant est le répertoire où se trouve le fichier `package.json`.

Et aussi, si nous, par exemple, utilisons vscode , le fichier spécifié dans ce champ sera lancé lorsque le débogueur est appelé ou lorsque la commande "execute" est exécutée.

L'extension ".js" peut être omise. C'est plutôt une conséquence de tous les cas d'utilisation possibles, donc ce n'est pas directement expliqué dans la documentation.

moteurs


Ce champ contient le tuple: {"node": version , "npm": version , ...}.

Je connais les champs "node" et "npm". Ils déterminent les versions de node.js et npm nécessaires au fonctionnement de notre application. Les versions sont vérifiées en exécutant la commande npm install.

La syntaxe standard pour déterminer la version des packages de dépendances est prise en charge: sans préfixe (version unique), le préfixe "~" (les deux premiers chiffres de la version doivent correspondre) et le préfixe "^" (seul le premier numéro de la version doit correspondre). S'il y a un préfixe, la version doit être supérieure ou égale à celle spécifiée dans ce champ. Juste une liste de versions; indication explicite plus, moins, ... etc. fonctionne également.

Clause de non-responsabilité "Npm install" vérifie les versions spécifiées dans les "moteurs" uniquement si le mode "strict moteur" est activé. Nous l'incluons pour chaque projet, en ajoutant le fichier .npmrc avec la ligne: «engine-strict = true». Il était une fois, "npm install" faisait cette vérification par défaut.

Certains conteneurs, au moins dans la documentation, écrivent que des versions appropriées seront utilisées par défaut. Dans ce cas, nous parlons d'Azure.

Un exemple:
 "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 }, 


"râteau" régulier



Et le roi est nu!

Il a été convenu à plusieurs reprises avec le client que la version requise de `node.js` devrait être au moins 8. Lorsque les versions initiales de l'application ont été livrées, tout a fonctionné. «Un jour» après la livraison de la nouvelle version chez le client, l'application a cessé de fonctionner. Tout a fonctionné dans nos tests.

Le problème était que dans cette version, nous avons commencé à utiliser des fonctionnalités prises en charge uniquement à partir de la version 8 node.js. Le champ «moteurs» n'était pas rempli, donc personne n'avait remarqué auparavant que le client avait une ancienne version de node.js. (Services Web Azure par défaut).

scripts


Le champ contient un tuple de la forme: {"script1": script1 , "script2": script2 , ...}.

Il existe des scripts standard qui s'exécutent dans une situation donnée. Par exemple, le script "install" s'exécutera après avoir exécuté "npm install". Il est très pratique, par exemple, de vérifier la disponibilité des programmes nécessaires au fonctionnement de l'application. Ou, par exemple, pour compresser tous les fichiers statiques disponibles via notre service Web afin qu'ils n'aient pas à être compressés à la volée.

Dans ce cas, vous ne pouvez pas vous limiter uniquement aux noms standard. Pour exécuter un script arbitraire, vous devez exécuter "npm run script-name ".

Il est pratique de rassembler tous les scripts utilisés en un seul endroit.

Un exemple:
  "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" } 


PS L'extension ".js" peut être omise dans la plupart des cas.


package-lock.json - aide à installer des versions spécifiques des dépendances, pas les «dernières»



Texte masqué
Git ou ne pas git? ..


Ce fichier est apparu dans npm relativement récemment. Son but est d'organiser la répétabilité de l'assemblage.

et encore un "râteau"


Mais je n'ai rien changé à mon programme! Hier, elle a travaillé!


Sur une machine homologue, l'application fonctionnait très bien. Sur un autre ordinateur dans un environnement identique, dans une application placée de git dans un nouveau répertoire, après avoir exécuté 'npm install', 'npm start' des erreurs jusqu'ici sans précédent sont apparues.

Le problème était dû au fait que le fichier 'package-lock.json' manquait dans le référentiel git. Par conséquent, lors de l'installation des packages, toutes les dépendances du deuxième niveau ou plus (naturellement, non écrites dans package.json) ont été installées aussi fraîches que possible. Sur l'ordinateur d'un collègue, tout allait bien. Un ensemble de versions incompatible a été sélectionné sur l'ordinateur testé.

package-lock.json - pour git!



Revenant de la digression. Le fichier 'package-lock.json' contient une liste de tous les modules installés localement pour notre application. La présence de ce fichier vous permet de recréer un ensemble univoque de versions de modules.

Résumé: n'oubliez pas de mettre en git et de l'inclure dans le fichier de livraison (installation) de l'application!

Utile: si le fichier 'package-lock.json' est manquant, mais qu'il existe un répertoire 'node_modules' avec tous les modules nécessaires, le fichier 'package-lock.json' peut être recréé:
 npm shrinkwrap rename npm-shrinkwrap.json package-lock.json 



Vous pouvez y mettre fin pour l'instant. Les informations non incluses sont la technique de simplification du code utilisée par notre équipe.

Si des erreurs sont détectées, je vais essayer de les corriger rapidement!

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


All Articles