De nombreux travailleurs bloquants ou célibataires non bloquants


9

Supposons qu'il existe un serveur HTTP qui accepte les connexions, puis il a en quelque sorte attendu que les en-têtes soient entièrement envoyés. Je me demande quelle est la façon la plus courante de l'implémenter et quels sont les autres avantages et inconvénients. Je ne peux que penser à ceux-ci:

Beaucoup de bloqueurs sont bons parce que:

  • Il est plus réactif.
  • Il est plus facile d'introduire de nouvelles connexions (les travailleurs les récupèrent eux-mêmes plutôt que des étrangers attendant de pouvoir l'ajouter à une liste synchronisée).
  • L'utilisation du processeur s'équilibre automatiquement (sans effort supplémentaire) à mesure que le nombre de connexions augmente et diminue.
  • Moins d'utilisation du processeur (les threads bloqués sont retirés de la boucle d'exécution et ne nécessitent aucune logique pour passer d'un client à l'autre).

Un seul travailleur non bloquant est bon parce que:

  • Utilise moins de mémoire.
  • Moins vulnérable aux clients paresseux (qui se connectent au serveur et envoient des en-têtes lentement ou n'envoient pas du tout).

Comme vous pouvez probablement le voir, à mon avis, plusieurs threads de travail semblent globalement une meilleure solution. Le seul problème est qu'il est plus facile d'attaquer un tel serveur.

Edit (plus de recherche): Une ressource que j'ai trouvée sur le Web (des milliers de threads et des E / S de blocage - l'ancienne méthode d'écriture des serveurs Java est à nouveau (et bien meilleure) par Paul Tyma) laisse entendre que l'approche de blocage est généralement meilleure mais Je ne sais toujours pas vraiment comment gérer les fausses connexions.

PS Ne suggère pas d'utiliser une bibliothèque ou des applications pour la tâche. Je suis plus intéressé à savoir comment cela fonctionne ou peut fonctionner plutôt que de le faire fonctionner.

PSS J'ai divisé la logique en plusieurs parties et celle-ci ne gère que l'acceptation des en-têtes HTTP. Ne les traite pas.


Voilà, il y a plusieurs années, j'ai écrit un serveur fileté avec blocage des E / S, car il était facile à écrire. Un collègue a écrit l'autre type, et cela a fonctionné admirablement. C'étaient deux formes de l'offre de produits majeure dans une entreprise où je travaillais. Pour les "clients paresseux" dans le scénario de blocage, vous pouvez avoir un délai d'attente sur la réception des données.

Réponses:


4

Il n'y a pas de solution miracle

En pratique cela dépend ...

tl; dr - solution facile, utilisez nginx ...

Blocage:

Par exemple, Apache utilise par défaut un schéma de blocage dans lequel le processus est bifurqué pour chaque connexion. Cela signifie que chaque connexion a besoin de son propre espace mémoire et la quantité de surcharge de changement de contexte augmente davantage à mesure que le nombre de connexions augmente. Mais l'avantage est qu'une fois la connexion fermée, le contexte peut être supprimé et toute / toute la mémoire peut être facilement récupérée.

Une approche multithread serait similaire dans la mesure où la surcharge de la commutation de contexte augmente avec le nombre de connexions, mais peut être plus efficace en mémoire dans un contexte partagé. Le problème avec une telle approche est qu'il est difficile de gérer la mémoire partagée d'une manière sûre. Les approches pour surmonter les problèmes de synchronisation de la mémoire incluent souvent leur propre surcharge, par exemple le verrouillage peut geler le thread principal sur les charges gourmandes en CPU, et l'utilisation de types immuables ajoute beaucoup de copie inutile de données.

AFAIK, l'utilisation d'une approche multi-processus sur un serveur HTTP bloquant est généralement préférée car il est plus sûr / plus simple de gérer / récupérer la mémoire d'une manière sûre. La récupération de place devient un non-problème lorsque la récupération de mémoire est aussi simple que l'arrêt d'un processus. Pour les processus de longue durée (c'est-à-dire un démon), cette caractéristique est particulièrement importante.

Alors que les frais généraux de changement de contexte peuvent sembler insignifiants avec un petit nombre de travailleurs, les inconvénients deviennent plus pertinents à mesure que la charge augmente jusqu'à des centaines à des milliers de connexions simultanées. Au mieux, le changement de contexte met à l'échelle O (n) le nombre de travailleurs présents, mais en pratique, il est très probablement pire.

Lorsque les serveurs qui utilisent le blocage peuvent ne pas être le choix idéal pour les charges lourdes d'E / S, ils sont idéaux pour les travaux gourmands en ressources processeur et le passage des messages est limité à un minimum.

Non bloquant:

Le non-blocage serait quelque chose comme Node.js ou nginx. Celles-ci sont particulièrement connues pour évoluer vers un nombre beaucoup plus élevé de connexions par nœud sous une charge gourmande en E / S. Fondamentalement, une fois que les utilisateurs ont atteint la limite supérieure de ce que les serveurs basés sur les threads / processus pouvaient gérer, ils ont commencé à explorer d'autres options. Ceci est autrement connu comme le problème C10K (c'est-à-dire la capacité à gérer 10 000 connexions simultanées).

Les serveurs asynchrones non bloquants partagent généralement beaucoup de caractéristiques avec une approche multi-thread avec verrouillage en ce sens que vous devez faire attention à éviter les charges gourmandes en CPU car vous ne voulez pas surcharger le thread principal. L'avantage est que la surcharge engendrée par le changement de contexte est essentiellement éliminée et avec le passage d'un seul message de contexte devient un non-problème.

Bien que cela puisse ne pas fonctionner pour de nombreux protocoles de mise en réseau, la nature sans état HTTP fonctionne particulièrement bien pour les architectures non bloquantes. En utilisant la combinaison d'un proxy inverse et de plusieurs serveurs HTTP non bloquants, il est possible d'identifier et d'acheminer autour des nœuds soumis à une forte charge.

Même sur un serveur qui n'a qu'un seul nœud, il est très courant que la configuration inclue un serveur par cœur de processeur pour maximiser le débit.

Tous les deux:

Le cas d'utilisation «idéal» serait une combinaison des deux. Un proxy inverse en façade dédié au routage des requêtes en haut, puis un mix de serveurs bloquants et non bloquants. Non bloquant pour les tâches IO comme la diffusion de contenu statique, le contenu de cache, le contenu html. Blocage pour les tâches gourmandes en CPU telles que l'encodage d'images / vidéos, la diffusion de contenu, la compression de nombres, les écritures de base de données, etc.

Dans ton cas:

Si vous ne faites que vérifier les en-têtes mais ne traitez pas réellement les demandes, ce que vous décrivez essentiellement est un proxy inverse. Dans un tel cas, j'opterais définitivement pour une approche asynchrone.

Je suggère de consulter la documentation du proxy inverse intégré nginx .

De côté:

J'ai lu l'article du lien que vous avez fourni et il est logique que l'async soit un mauvais choix pour leur implémentation particulière. Le problème peut être résumé en une seule déclaration.

A constaté que lors du basculement entre les clients, le code d'enregistrement et de restauration des valeurs / état était difficile

Ils construisaient une plate-forme d'État. Dans un tel cas, une approche asynchrone signifierait que vous devriez constamment sauvegarder / charger l'état chaque fois que le contexte change (c'est-à-dire lorsqu'un événement se déclenche). De plus, du côté SMTP, ils font beaucoup de travail gourmand en CPU.

On dirait qu'ils avaient une assez mauvaise compréhension de l'async et, par conséquent, ont fait beaucoup de mauvaises hypothèses.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.