Maison > Article > interface Web > Étude d'E/S asynchrones Node.js notes_node.js
Le terme « asynchrone » est devenu très populaire dans la vague du Web 2.0, qui a balayé le Web avec Javascript et AJAX. Mais dans la plupart des langages de programmation de haut niveau, l’asynchronie est rare. PHP incarne le mieux cette fonctionnalité : non seulement il bloque de manière asynchrone, mais il ne permet même pas que le multithreading soit exécuté de manière synchrone. Cet avantage aide les programmeurs à écrire une logique métier de manière séquentielle, mais dans les applications réseau complexes, le blocage empêche une meilleure concurrence.
Côté serveur, les E/S sont très coûteuses, et les E/S distribuées sont encore plus chères. Ce n'est que lorsque le backend peut répondre rapidement aux ressources que l'expérience front-end peut s'améliorer. Node.js est la première plate-forme à utiliser l'asynchrone comme principale méthode de programmation et concept de conception. Avec les E/S asynchrones, les E/S sont pilotées par les événements et monothread, qui forment le ton de Node. Cet article présentera comment Node implémente les E/S asynchrones.
1.Concepts de base
"Asynchrone" et "non bloquant" semblent identiques. En termes d'effets pratiques, les deux atteignent l'objectif du parallélisme. Mais du point de vue des E/S du noyau informatique, il n’existe que deux méthodes : bloquante et non bloquante. Donc asynchrone/synchrone et bloquant/non bloquant sont en fait deux choses différentes.
1.1 E/S bloquantes et E/S non bloquantes
Une caractéristique du blocage des E/S est qu'après l'appel, l'appel doit attendre que toutes les opérations soient terminées au niveau du noyau du système avant la fin de l'appel. En prenant comme exemple la lecture d'un fichier sur le disque, cet appel se termine une fois que le noyau système a terminé la recherche sur le disque, lit les données et copie les données dans la mémoire.
Le blocage des E/S oblige le processeur à attendre les E/S, ce qui fait perdre du temps d'attente et la puissance de traitement du processeur ne peut pas être pleinement utilisée. La caractéristique des E/S non bloquantes est qu'elles reviennent immédiatement après l'appel. Après le retour, la tranche de temps CPU peut être utilisée pour traiter d'autres transactions. Puisque l'E/S complète n'est pas terminée, ce qui est renvoyé immédiatement ne sont pas les données attendues par la couche métier, mais uniquement l'état de l'appel en cours. Afin d'obtenir des données complètes, l'application doit appeler à plusieurs reprises l'opération d'E/S pour confirmer si elle est terminée (c'est-à-dire une interrogation). Les techniques de sondage sont les suivantes :
1.lire : Vérifier l'état des E/S via des appels répétés est le moyen le plus primitif et le moins performant
2.select : Amélioration de la lecture, à en juger par l'état de l'événement sur le descripteur de fichier. L'inconvénient est que le nombre maximum de descripteurs de fichiers est limité
3.poll : Amélioration de la sélection, utilisation de liste chaînée pour éviter la limite de nombre maximum, mais lorsqu'il y a beaucoup de descripteurs, les performances sont encore très faibles
4.epoll : Si aucun événement d'E/S n'est détecté lors de la saisie de l'interrogation, il se mettra en veille jusqu'à ce qu'un événement se produise pour le réveiller. Il s'agit du mécanisme de notification d'événements d'E/S le plus efficace actuellement sous Linux
Le polling répond au besoin d'E/S non bloquantes pour assurer une acquisition complète des données, mais pour l'application, il ne peut encore être considéré que comme une sorte de synchronisation, car il faut encore attendre le retour des E/S complètement. Pendant la période d'attente, le processeur est utilisé soit pour parcourir l'état du descripteur de fichier, soit pour dormir et attendre qu'un événement se produise.
1.2 E/S asynchrones dans l'idéal et la réalité
Les E/S asynchrones parfaites devraient se produire lorsque l'application lance un appel non bloquant et peut traiter directement la tâche suivante sans interrogation. Il lui suffit de transmettre les données à l'application via un signal ou un rappel une fois l'E/S terminée. complété. .
Les E/S asynchrones ont en réalité différentes implémentations sous différents systèmes d'exploitation. Par exemple, la plate-forme *nix utilise un pool de threads personnalisé et la plate-forme Windows utilise le modèle IOCP. Node fournit libuv comme couche d'encapsulation abstraite pour encapsuler le jugement de compatibilité de la plate-forme et garantir que l'implémentation des E/S asynchrones du nœud supérieur et de la plate-forme inférieure est indépendante. De plus, il convient de souligner que nous mentionnons souvent que Node est monothread. Cela signifie uniquement que Javascript est exécuté dans un seul thread. Il existe un pool de threads qui effectue réellement les tâches d'E/S à l'intérieur de Node.
2. E/S asynchrones du nœud
Boucle d'événement 2.1
Le modèle d'exécution de Node est en fait une boucle d'événements. Lorsque le processus démarre, Node crée une boucle infinie et chaque exécution du corps de la boucle devient un Tick. Chaque processus Tick doit vérifier s'il y a des événements en attente de traitement et, si tel est le cas, récupérer l'événement et ses fonctions de rappel associées. S'il existe des fonctions de rappel associées, exécutez-les, puis entrez dans la boucle suivante. S'il n'y a plus d'événements à gérer, quittez le processus.
2.2 Observateur
Il y a plusieurs observateurs dans chaque boucle d'événements, et vous pouvez déterminer s'il y a des événements à traiter en interrogeant ces observateurs. La boucle d’événements est un modèle producteur/consommateur typique. Dans Node, les événements proviennent principalement de requêtes réseau, d'E/S de fichiers, etc. Ces événements ont des observateurs d'E/S réseau correspondants, des observateurs d'E/S de fichiers, etc. La boucle d'événements récupère les événements des observateurs et les traite.
2.3 Objet de requête
Dans le processus de transition depuis Javascript initiant un appel au noyau complétant une opération d'E/S, il existe un produit intermédiaire appelé objet de requête. En prenant comme exemple la méthode fs.open() la plus simple sous Windows (pour ouvrir un fichier et obtenir un descripteur de fichier en fonction du chemin et des paramètres spécifiés), appeler le système depuis JS vers le module intégré via libuv appelle en fait uv_fs_open( ) méthode. Pendant le processus d'appel, un objet de requête FSReqWrap est créé. Les paramètres et méthodes transmis depuis la couche JS sont encapsulés dans cet objet de requête. La fonction de rappel qui nous préoccupe le plus est définie sur l'attribut oncompete_sym de cet objet. Une fois l'objet encapsulé, l'objet FSReqWrap est poussé dans le pool de threads pour attendre son exécution.
À ce stade, l'appel JS revient immédiatement et le thread JS peut continuer à effectuer les opérations suivantes. L'opération d'E/S en cours attend d'être exécutée dans le pool de threads, ce qui termine la première phase de l'appel asynchrone.
2.4 Rappel d'exécution
La notification de rappel est la deuxième phase des E/S asynchrones. Une fois l'opération d'E/S dans le pool de threads appelée, les résultats obtenus seront stockés, puis l'IOCP sera informé que l'opération d'objet en cours est terminée et le thread sera renvoyé au pool de threads. Lors de chaque exécution de Tick, l'observateur d'E/S de la boucle d'événement appellera la méthode appropriée pour vérifier s'il y a une requête terminée dans le pool de threads. Si c'est le cas, l'objet de requête sera ajouté à la file d'attente des E/S. observateur. Ensuite, traitez-le comme un événement.
3. API asynchrone sans E/S
Il existe également des API asynchrones dans Node qui n'ont rien à voir avec les E/S, telles que les timers setTimeout(), setInterval(), process.nextTick() et setImmdiate() qui effectuent des tâches de manière asynchrone immédiatement, etc. Ici est une brève introduction.
API de minuterie 3.1
Les API côté navigateur de setTimeout() et setInterval() sont cohérentes. Leurs principes de mise en œuvre sont similaires aux E/S asynchrones, mais elles ne nécessitent pas la participation du pool de threads d'E/S. Le timer créé en appelant l'API timer sera inséré dans une arborescence rouge-noir à l'intérieur de l'observateur du timer. Chaque tick de la boucle d'événement supprimera de manière itérative l'objet timer de l'arborescence rouge-noir et vérifiera si le timer a dépassé If If. il dépasse, un événement est formé et la fonction de rappel est exécutée immédiatement. Le principal problème du timer est que son timing n'est pas particulièrement précis (en millisecondes, dans les limites de tolérance).
3.2 API d'exécution de tâches asynchrones immédiates
Avant l'apparition de Node, de nombreuses personnes pouvaient appeler ceci afin d'exécuter une tâche immédiatement et de manière asynchrone :
En raison des caractéristiques de la boucle d'événements, le minuteur n'est pas assez précis, et l'utilisation d'un minuteur nécessite l'utilisation d'un arbre rouge-noir, et la complexité temporelle des diverses opérations est O(log(n)). La méthode process.nextTick() mettra uniquement la fonction de rappel dans la file d'attente et la retirera pour exécution au prochain tour de Tick. La complexité est O(1), ce qui est plus efficace.
Il existe également une méthode setImmediate() similaire à la méthode ci-dessus, qui retarde l'exécution de la fonction de rappel. Cependant, le premier a une priorité plus élevée que le second, car la boucle d'événements vérifie les observateurs dans l'ordre. De plus, les fonctions de rappel des premiers sont stockées dans un tableau, et chaque tour de Tick exécutera toutes les fonctions de rappel du tableau ; les résultats des seconds sont stockés dans une liste chaînée, et une seule fonction de rappel sera exécutée par tour de tique.
4. Serveur événementiel et performant
L'exemple précédent utilisait fs.open() pour expliquer comment Node implémente les E/S asynchrones. En fait, Node utilise également des E/S asynchrones pour traiter les sockets réseau, ce qui constitue également la base sur laquelle Node crée des serveurs Web. Les modèles de serveurs classiques incluent :
1. Synchrone : une seule demande peut être traitée à la fois, et le reste des demandes est en attente
2. Par processus/par requête : démarrez un processus pour chaque requête, mais les ressources système sont limitées et non évolutives
3. Par thread/par requête : démarrez un thread pour chaque requête. Les threads sont plus légers que les processus, mais chaque thread occupe une certaine quantité de mémoire Lorsque de grosses requêtes simultanées arrivent, la mémoire sera rapidement utilisée
Le célèbre Apache utilise un format par thread/par requête, c'est pourquoi il est difficile de gérer une concurrence élevée. Node gère les requêtes de manière événementielle, ce qui permet d'économiser la surcharge liée à la création et à la destruction des threads. Dans le même temps, étant donné que le système d'exploitation a moins de threads lors de la planification des tâches, le coût du changement de contexte est également très faible. Node traite les requêtes de manière ordonnée, même avec un grand nombre de connexions.
Le célèbre serveur Nginx a également abandonné la méthode multi-threading et a adopté la même méthode événementielle que Node. De nos jours, Nginx a un grand potentiel pour remplacer Apache. Nginx est écrit en C pur et offre des performances élevées, mais il ne convient que pour les serveurs Web, le proxy inverse ou l'équilibrage de charge, etc. Node peut créer les mêmes fonctions que Nginx, peut également gérer diverses activités spécifiques et ses propres performances sont également bonnes. Dans les projets réels, nous pouvons combiner leurs avantages respectifs pour obtenir les meilleures performances de l'application.