Maison > Article > Opération et maintenance > Pourquoi les performances de nginx sont-elles bonnes ?
Une fois nginx démarré, il s'exécutera en arrière-plan en tant que démon dans le système Unix. Le processus d'arrière-plan comprend un processus maître et plusieurs processus de travail. Nous pouvons également désactiver manuellement le mode d'arrière-plan, laisser nginx s'exécuter au premier plan et configurer nginx pour annuler le processus maître, afin que nginx puisse s'exécuter en mode mono-processus.
Évidemment, nous ne ferons certainement pas cela dans un environnement de production, donc la désactivation du mode arrière-plan est généralement utilisée pour le débogage. Dans les chapitres suivants, nous expliquerons en détail comment déboguer nginx.
Nous pouvons donc voir que nginx fonctionne de manière multi-processus. Bien sûr, nginx prend également en charge le multi-threading, mais notre méthode principale est toujours la méthode multi-processus, qui est également la méthode par défaut de. nginx. Il y a de nombreux avantages à utiliser le multi-processus dans nginx, je vais donc principalement expliquer le mode multi-processus de nginx.
Comme mentionné tout à l'heure, après le démarrage de nginx, il y aura un processus maître et plusieurs processus de travail. Le processus maître est principalement utilisé pour gérer les processus de travail, notamment : recevoir des signaux du monde extérieur, envoyer des signaux à chaque processus de travail, surveiller l'état d'exécution du processus de travail et redémarrer automatiquement un nouveau processus de travail lorsque le processus de travail se termine (sous circonstances anormales).
Les événements réseau de base sont gérés dans le processus de travail. Plusieurs processus de travail sont peer-to-peer. Ils sont en concurrence égale pour les demandes des clients et chaque processus est indépendant les uns des autres. Une demande ne peut être traitée que dans un seul processus de travail, et un processus de travail ne peut pas traiter les demandes provenant d'autres processus. Le nombre de processus de travail peut être défini. Généralement, nous le définirons pour qu'il soit cohérent avec le nombre de cœurs de processeur de la machine. La raison en est indissociable du modèle de processus et du modèle de traitement des événements de nginx.
Après le démarrage de nginx, si nous voulons faire fonctionner nginx, que devons-nous faire ?
De ce qui précède, nous pouvons voir que le maître gère le processus de travail, nous n'avons donc besoin de communiquer qu'avec le processus maître. Le processus maître recevra des signaux du monde extérieur et fera ensuite différentes choses en fonction de ces signaux. Donc, si nous voulons contrôler nginx, il nous suffit d'envoyer un signal au processus maître via kill. Par exemple, kill -HUP pid indique à nginx de redémarrer nginx normalement. Nous utilisons généralement ce signal pour redémarrer nginx ou recharger la configuration. Parce qu'il redémarre correctement, le service n'est pas interrompu. Que fait le processus maître après avoir reçu le signal HUP ?
Tout d'abord, après avoir reçu le signal, le processus maître rechargera le fichier de configuration, puis démarrera un nouveau processus de travail et enverra des signaux à tous les anciens processus de travail pour leur dire qu'ils peuvent prendre leur retraite honorablement.
Après le démarrage du nouveau travailleur, il commence à recevoir de nouvelles demandes, tandis que l'ancien travailleur cesse de recevoir de nouvelles demandes après avoir reçu le signal du maître, et toutes les demandes non traitées dans le processus en cours une fois le traitement complet des demandes terminé. , sortie.
Bien sûr, l'envoi de signaux directement au processus maître est une méthode de fonctionnement plus ancienne. Après la version 0.8 de nginx, il a introduit une série de paramètres de ligne de commande pour faciliter notre gestion. Par exemple, ./nginx -s reload consiste à redémarrer nginx et ./nginx -s stop consiste à empêcher nginx de s'exécuter.
Comment faire ?
Prenons reload comme exemple. Nous voyons que lors de l'exécution de la commande, nous démarrons un nouveau processus nginx, et le nouveau processus nginx connaîtra notre objectif après avoir analysé le paramètre reload. nginx pour recharger le fichier de configuration. Il enverra un signal au processus maître, puis l'action suivante est la même que si nous envoyions directement le signal au processus maître.
Maintenant, nous savons ce que nginx fait en interne lorsque nous utilisons nginx. Alors, comment le processus de travail traite-t-il la demande ? Comme nous l'avons mentionné précédemment, les processus de travail sont égaux et chaque processus a la même opportunité de traiter les demandes. Lorsque nous fournissons un service http sur le port 80 et qu'une demande de connexion arrive, chaque processus peut gérer la connexion. Comment faire cela ?
Tout d'abord, chaque processus de travail est dérivé du processus maître. Dans le processus maître, le socket (listenfd) qui doit être écouté est d'abord établi, puis plusieurs processus de travail sont dérivés. Le Listenfd de tous les processus de travail deviendra lisible lorsqu'une nouvelle connexion arrive. Pour garantir qu'un seul processus gère la connexion, tous les processus de travail récupèrent accept_mutex avant d'enregistrer l'événement de lecture Listenfd. Le processus qui récupère le mutex enregistre l'événement de lecture Listenfd. accept dans l'événement read pour accepter la connexion.
Lorsqu'un processus de travail accepte la connexion, il commence à lire la demande, à analyser la demande, à traiter la demande, à générer des données, puis à les renvoyer au client, et enfin à déconnecter une demande aussi complète. c'est ça. Nous pouvons voir qu'une demande est entièrement traitée par le processus de travail et n'est traitée que dans un seul processus de travail.
Modèle multi-thread VS modèle multi-processus, c'est une question !
Alors, quels sont les avantages de l'adoption par Nginx de ce modèle de processus ? Bien entendu, les avantages seront certainement nombreux. Tout d'abord, pour chaque processus de travail, il s'agit d'un processus indépendant et n'a pas besoin d'être verrouillé, donc la surcharge causée par le verrouillage est éliminée. En même temps, cela sera beaucoup plus pratique lors de la programmation et de la recherche de problèmes. Deuxièmement, l'utilisation de processus indépendants ne s'affectera pas les uns les autres. Après la fin d'un processus, les autres processus fonctionnent toujours et le service ne sera pas interrompu. Le processus maître démarrera rapidement un nouveau processus de travail. Bien sûr, si le processus de travail se termine anormalement, il doit y avoir un bogue dans le programme. Une sortie anormale entraînera l'échec de toutes les demandes sur le travailleur actuel, mais cela n'affectera pas toutes les demandes, le risque est donc réduit. Bien sûr, les avantages sont nombreux et tout le monde peut en profiter lentement.
Ce qui précède a beaucoup parlé du modèle de processus de nginx. Voyons ensuite comment nginx gère les événements.
Quelqu'un peut demander, nginx utilise une méthode multi-travailleurs pour traiter les requêtes. Il n'y a qu'un seul thread principal dans chaque travailleur, donc le nombre de simultanéités pouvant être traitées est très limité. nombre de simultanéités, comment obtenir une simultanéité élevée ? Non, c'est le génie de nginx. nginx utilise une méthode asynchrone et non bloquante pour traiter les requêtes. En d'autres termes, nginx peut gérer des milliers de requêtes en même temps.
Pensez à la méthode de travail commune d'Apache (Apache a également une version asynchrone non bloquante, mais elle n'est pas couramment utilisée car elle entre en conflit avec certains de ses propres modules). thread de travail.Quand Lorsque le nombre de concurrence atteint des milliers, des milliers de threads traitent les requêtes en même temps. C'est un grand défi pour le système d'exploitation. L'utilisation de la mémoire causée par les threads est très importante et la surcharge du processeur causée par le changement de contexte de thread est naturellement très importante. Les performances ne peuvent pas être améliorées et ces surcharges n'ont aucun sens.
Blocage synchrone VS non-blocage asynchrone
Pourquoi nginx peut-il utiliser le non-blocage asynchrone pour le gérer, ou qu'est-ce que le non-blocage asynchrone exactement ? Revenons au point de départ et regardons le processus complet d’une demande. Tout d'abord, la demande arrive, une connexion est établie, puis les données sont reçues. Après réception des données, les données sont envoyées. Spécifique à la couche inférieure du système, ce sont les événements de lecture et d'écriture. Lorsque les événements de lecture et d'écriture ne sont pas prêts, ils seront forcément inopérables. Si vous ne l'appelez pas de manière non bloquante, vous devrez le faire. bloquer l'appel. Si l'événement n'est pas prêt, vous ne pouvez qu'attendre. D'accord, vous pouvez continuer lorsque l'événement est prêt. Les appels bloquants entreront dans le noyau et attendront, et le CPU sera utilisé par d'autres. Pour les travailleurs monothread, cela ne convient évidemment pas. Lorsqu'il y a plus d'événements réseau, tout le monde attend et personne n'utilise le CPU quand c'est le cas. utilisation du processeur inactive. Naturellement, le taux ne peut pas augmenter, encore moins une concurrence élevée.
D'accord, vous avez dit en ajoutant le nombre de processus, quelle est la différence entre cela et le modèle de thread d'Apache ? Faites attention à ne pas ajouter de changements de contexte inutiles. Par conséquent, dans nginx, le blocage des appels système est le plus tabou. Ne bloquez pas, alors ce n'est pas bloquant. Non bloquant signifie que si l'événement n'est pas prêt, il reviendra immédiatement à EAGAIN pour vous dire que l'événement n'est pas encore prêt. Pourquoi paniquez-vous plus tard ? D'accord, après un certain temps, vérifiez à nouveau l'événement jusqu'à ce qu'il soit prêt. Pendant cette période, vous pouvez d'abord faire autre chose, puis vérifier si l'événement est prêt. Bien qu'il ne soit plus bloqué, vous devez vérifier l'état de l'événement de temps en temps. Vous pouvez faire plus de choses, mais la surcharge n'est pas minime. Par conséquent, il existe un mécanisme de traitement d'événements asynchrone non bloquant et les appels système spécifiques sont des appels système tels que select/poll/epoll/kqueue.
Ils fournissent un mécanisme qui vous permet de surveiller plusieurs événements en même temps. Les appeler est bloquant, mais vous pouvez définir un délai d'attente. Dans le délai d'attente, si un événement est prêt, il reviendra. Ce mécanisme résout simplement nos deux problèmes ci-dessus. Prenons epoll comme exemple (dans les exemples suivants, nous utilisons souvent epoll comme exemple pour représenter ce type de fonction. Lorsque l'événement n'est pas prêt, il est placé dans epoll ). l'événement est prêt, nous passons en lecture et en écriture. Lorsque la lecture et l'écriture renvoient EAGAIN, nous l'ajoutons à nouveau à epoll. De cette façon, tant qu'un événement est prêt, nous le traiterons, et seulement lorsque tous les événements ne sont pas prêts, nous attendrons dans epoll. De cette façon, nous pouvons gérer un grand nombre de requêtes simultanées. Bien entendu, les requêtes simultanées font ici référence à des requêtes non traitées. Il n'y a donc qu'un seul thread, donc bien sûr, il n'y a qu'une seule requête qui peut être traitée en même temps. il suffit de basculer continuellement entre les requêtes. Voilà, le commutateur a été volontairement abandonné car l'événement asynchrone n'était pas prêt. Il n'y a aucun coût pour changer ici. Vous pouvez comprendre cela comme le traitement de plusieurs événements préparés en boucle, ce qui est effectivement le cas.
Par rapport au multi-threading, cette méthode de traitement d'événements présente de grands avantages. Il n'est pas nécessaire de créer des threads, chaque requête occupe très peu de mémoire, il n'y a pas de changement de contexte et le traitement des événements est très léger. Quel que soit le nombre de simultanéités, cela n’entraînera pas de gaspillage inutile de ressources (changement de contexte). Une plus grande concurrence ne fera que prendre plus de mémoire. J'ai déjà testé le nombre de connexions. Sur une machine dotée de 24 Go de mémoire, le nombre de requêtes simultanées traitées a atteint plus de 2 millions. Les serveurs réseau d'aujourd'hui utilisent essentiellement cette méthode, ce qui est également la principale raison pour laquelle nginx a des performances élevées.
Nous avons déjà dit qu'il est recommandé de définir le nombre de travailleurs sur le nombre de cœurs de processeur. C'est facile à comprendre ici. Plus de travailleurs ne feront qu'entraîner une concurrence pour les ressources du processeur, provoquant ainsi un changement de contexte inutile.
De plus, afin de mieux utiliser les fonctionnalités multicœurs, nginx fournit une option de liaison par affinité CPU Nous pouvons lier un certain processus à un certain cœur, afin qu'il n'y ait aucun problème dû au changement de processus. .Venez échec du cache. De petites optimisations comme celle-ci sont très courantes dans nginx, et cela illustre également les efforts minutieux de l'auteur de nginx. Par exemple, lorsque nginx compare des chaînes de 4 octets, il convertit les 4 caractères en type int, puis les compare pour réduire le nombre d'instructions CPU, etc.
Maintenant, nous savons pourquoi nginx choisit un tel modèle de processus et un tel modèle d'événement. Pour un serveur Web de base, il existe généralement trois types d'événements : les événements réseau, les signaux et les minuteries. D'après l'explication ci-dessus, nous savons que les événements de réseau peuvent être bien résolus grâce au non-blocage asynchrone. Comment gérer les signaux et les minuteries ?
Tout d’abord, le traitement du signal.
Pour nginx, certains signaux spécifiques représentent des significations spécifiques. Le signal interrompra l'exécution en cours du programme et poursuivra l'exécution après avoir changé l'état. S'il s'agit d'un appel système, cela peut entraîner l'échec de l'appel système et nécessiter une nouvelle saisie. Concernant le traitement du signal, vous pouvez étudier quelques ouvrages professionnels, je n’entrerai donc pas dans les détails ici. Pour nginx, si nginx attend un événement (pendant epoll_wait), si le programme reçoit un signal, une fois la fonction de traitement du signal traitée, epoll_wait renverra une erreur, puis le programme pourra à nouveau entrer l'appel epoll_wait.
Jetons également un coup d’œil à la minuterie. Puisque epoll_wait et d'autres fonctions peuvent définir un délai d'attente lorsqu'elles sont appelées, nginx utilise ce délai d'attente pour implémenter le minuteur. Les événements de minuterie dans nginx sont placés dans une arborescence rouge-noire qui maintient les minuteries. Chaque fois avant d'entrer dans epoll_wait, la durée minimale de tous les événements de minuterie est obtenue à partir de l'arborescence rouge-noir et le délai d'expiration de epoll_wait est calculé après. temps.
Ainsi, lorsqu'aucun événement n'est généré et qu'il n'y a pas de signal d'interruption, epoll_wait expirera, c'est-à-dire que l'événement timer est arrivé. À ce stade, nginx vérifiera tous les événements de délai d'attente, définira leur statut sur délai d'attente, puis gérera l'événement réseau. Cela montre que lorsque nous écrivons du code nginx, lors du traitement de la fonction de rappel d'un événement réseau, la première chose que nous faisons habituellement est de déterminer le délai d'attente, puis de gérer l'événement réseau.
Pour plus de connaissances sur Nginx, visitez la colonne Tutoriel d'utilisation de Nginx !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!