Pourquoi les serveurs Web asynchrones sont-ils apparus?

Bonjour à tous. En contact avec Vladislav Rodin. Actuellement, je dirige le cours d'architecte à charge élevée d'OTUS et j'enseigne également des cours sur l'architecture logicielle.

En plus d'enseigner, comme vous pouvez le voir, j'ai écrit pour un blog des droits d'auteur OTUS Habré et l'article d'aujourd'hui je veux coïncider avec le début du cours «Administrateur Linux» , qui est maintenant ouvert à l'ensemble.





introduction


Pourquoi l'application Web ralentit-elle et ne retient-elle pas la charge? Les développeurs, qui ont été les premiers à rencontrer cette question et à mener des recherches sur certains systèmes, sont arrivés à la conclusion décevante qu'optimiser une logique métier ne serait pas suffisant à lui seul. La réponse à cette question se situe à un niveau inférieur - au niveau du système d'exploitation. Pour que votre application puisse supporter la charge, il est nécessaire de revoir son concept architectural de manière à fonctionner efficacement à ce niveau. Cela a conduit à l'émergence de serveurs Web asynchrones.

Malheureusement, je n'ai pas pu trouver un seul matériau qui me permette de restaurer toutes les relations causales dans l'évolution des serveurs Web à la fois. C'est ainsi que l'idée d'écrire cet article est venue, qui, je l'espère, deviendra un tel matériau.

Fonctionnalités du système d'exploitation Linux


Avant de parler des modèles de serveurs web, je me permets de rappeler quelques fonctionnalités des processus et des threads sous Linux. Nous en aurons besoin pour analyser les avantages et les inconvénients des modèles ci-dessus.

Changement de contexte


Très probablement, tout utilisateur qui sait qu'un seul programme peut être exécuté sur un cœur de processeur à la fois demandera: "Pourquoi peut-on lancer 20 programmes sur mon processeur à 4 cœurs à la fois?".

En fait, cela est dû au fait que le multitâche préemptif a lieu . Le système d'exploitation alloue un certain quantum de temps (~ 50 μs) et met le programme à exécuter sur le noyau pendant ce temps. Une fois le temps écoulé, le changement de contexte est interrompu et commuté. Autrement dit, le système d'exploitation met simplement le prochain programme à exécuter. Étant donné que la commutation se produit fréquemment, nous avons l'impression que tous les programmes fonctionnent simultanément. Faites attention à la fréquence de commutation élevée, cela sera important pour la présentation suivante.

Le changement de contexte a été mentionné ci-dessus. Que comprend-il? Lors de la commutation du contexte, il est nécessaire de sauvegarder les registres du processeur, d'effacer son pipeline d'instructions, de sauvegarder les régions mémoire allouées au processus. En général, l'opération est assez coûteuse. Cela prend ~ 0,5 μs, tandis que l'exécution d'une simple ligne de code est ~ 1 ns. De plus, avec une augmentation du nombre de processus par cœur de processeur, la surcharge pour le changement de contexte augmentera.

Modèles de serveur Web


Actuellement, les modèles de serveur Web suivants existent:

  • ouvrier
  • préfork
  • asynchrone
  • combiné


Discutons chacun d'eux séparément.

Travailleur et préfork


Historiquement, avec ces modèles, tout a commencé. L'essence est très simple : un client vient à nous, nous sélectionnons pour lui un gestionnaire distinct, qui traite le client entrant du début à la fin. Un gestionnaire peut être un processus (préfork) ou un thread (travailleur). Un exemple d'un tel serveur Web est le célèbre Apache.

Je ferai une réservation tout de suite: créer un nouveau gestionnaire pour chaque client coûte cher. Premièrement, avec un nombre constant de cœurs, une augmentation du nombre de processeurs entraîne une augmentation de la latence (due aux changements de contexte). Deuxièmement, la quantité de mémoire requise augmente linéairement avec l'augmentation du nombre de clients, car même si vous utilisez des threads de partage de mémoire, chaque thread a sa propre pile. Ainsi, le nombre de clients traités simultanément est limité.la taille du pool, qui, à son tour, dépend du nombre de cœurs de processeur. Le problème est résolu en utilisant des méthodes de mise à l'échelle verticale.

Un autre inconvénient fondamental de ces serveurs est l'utilisation non optimale des ressources. Les processus (ou threads) sont inactifs la plupart du temps . Imaginez la situation suivante: pendant le traitement du client, il est nécessaire de prendre des données du disque dur, de faire une demande dans la base de données ou d'écrire quelque chose sur le réseau. Étant donné que la lecture à partir d'un disque dur sous Linux est une opération de blocage , le processus (ou thread) se bloque en attendant une réponse, mais il participe toujours à l'allocation du temps processeur.

Worker vs prefork


Worker et prefork ont ​​quelques différences fondamentales. Les flux sont un peu plus économiques en mémoire car ils les partagent. Pour la même raison, un changement de contexte entre eux est plus facile qu'entre processus. Cependant, dans le cas d'un travailleur, le code devient multi-thread, car les threads doivent être synchronisés. En conséquence, nous obtenons tous les «charmes» du code multi-thread: il devient plus difficile de l'écrire, le lire, le tester et le déboguer.

Modèle asynchrone


Ainsi, le travailleur et la préfork ne permettent pas de traiter un grand nombre de clients en même temps en raison de la taille limitée du pool et n'utilisent pas de manière optimale les ressources en raison de la commutation de contexte et du blocage des appels système. Comme vous pouvez le voir, le problème est le multithreading et un planificateur de système d'exploitation lourd. Cela conduit à l'idée suivante: traitons les clients dans un seul thread, mais la chargeons à 100%.

Ces serveurs sont basés sur une boucle d'événement et un modèle de réacteur ( machine d'événement ). Le code client, initiant une opération d'E / S, enregistre un rappeldans une file d'attente prioritaire (la priorité est le temps de préparation). La boucle d'événements interroge les descripteurs en attente d'E / S, puis met à jour la priorité (si disponible). De plus, la boucle d'événements extrait les événements de la file d'attente prioritaire, ce qui oblige les rappels à revenir à la boucle d'événements à la fin.

Ce modèle vous permet de gérer un grand nombre de clients, en évitant la surcharge pour changer de contexte . Ce modèle n'est pas parfait et présente plusieurs inconvénients. Premièrement, pas plus d'un cœur de processeur n'est consommé , car il n'y a qu'un seul processus. Ceci est traité en utilisant un modèle combiné, qui sera discuté ci-dessous. Deuxièmement, les clients sont connectés par ce processus.. Le code doit être écrit avec soin. Les fuites de mémoire, les erreurs conduisent au fait que tous les clients tombent en même temps. De plus, ce processus ne doit être bloqué par rien, le rappel ne doit pas consister à résoudre certaines tâches difficiles, car tous les clients seront bloqués. Troisièmement, le code asynchrone est beaucoup plus compliqué . Il est nécessaire d'enregistrer un rappel supplémentaire que les données ne viendront pas, pour résoudre le problème de branchement correct, etc.

Modèle combiné


Ce modèle est utilisé dans de vrais serveurs. Ce modèle possède un pool de processus, chacun ayant un pool de threads, chacun utilisant à son tour un modèle de traitement basé sur une entrée-sortie asynchrone. Nginx présente un modèle combiné.

Conclusion


Ainsi, en nous tournant vers les bases du système d'exploitation, nous avons examiné les différences conceptuelles entre les modèles de serveur Web utilisés dans Apache et Nginx. Chacun d'eux a ses propres avantages et inconvénients, de sorte que leur combinaison est souvent utilisée dans la production.

L'idée du traitement asynchrone a encore évolué: au niveau des plates-formes linguistiques, les concepts de fils / fibres / goroutines verts sont apparus, qui vous permettent de "cacher sous le capot" l'asynchronie des entrées et des sorties, laissant le développeur satisfait d'un beau code synchrone. Cependant, ce concept mérite un article séparé.



En savoir plus sur le cours.



All Articles