Viele blockierende VS einzelne nicht blockierende Arbeiter


9

Angenommen, es gibt einen HTTP-Server, der Verbindungen akzeptiert und dann irgendwie darauf wartet, dass die Header vollständig gesendet werden. Ich frage mich, was die häufigste Art der Implementierung ist und welche Vor- und Nachteile dies hat. Ich kann nur an diese denken:

Viele blockierende Arbeiter sind gut, weil:

  • Es ist reaktionsschneller.
  • Einfachere Einführung neuer Verbindungen (Mitarbeiter nehmen sie selbst auf, anstatt zu warten, bis sie einer synchronisierten Liste hinzugefügt werden können).
  • Die CPU-Auslastung wird automatisch (ohne zusätzlichen Aufwand) ausgeglichen, wenn die Anzahl der Verbindungen zunimmt und abnimmt.
  • Geringere CPU-Auslastung (blockierte Threads werden aus der Ausführungsschleife entfernt und erfordern keine Logik zum Wechseln zwischen Clients).

Ein einzelner nicht blockierender Arbeiter ist gut, weil:

  • Verbraucht weniger Speicher.
  • Weniger anfällig für faule Clients (die eine Verbindung zum Server herstellen und Header langsam oder gar nicht senden).

Wie Sie wahrscheinlich sehen können, scheinen meiner Meinung nach mehrere Worker-Threads insgesamt eine etwas bessere Lösung zu sein. Das einzige Problem dabei ist, dass es einfacher ist, einen solchen Server anzugreifen.

Bearbeiten (weitere Nachforschungen): Einige Ressourcen, die ich im Web gefunden habe ( Tausende von Threads und Blockieren von E / A - Die alte Methode zum Schreiben von Java-Servern ist wieder neu (und viel besser) von Paul Tyma) deuten darauf hin, dass der Blockierungsansatz im Allgemeinen besser ist, aber Ich weiß immer noch nicht wirklich, wie ich mit gefälschten Verbindungen umgehen soll.

PS Schlagen Sie nicht vor, eine Bibliothek oder Anwendungen für die Aufgabe zu verwenden. Ich bin mehr daran interessiert zu wissen, wie es tatsächlich funktioniert oder funktioniert, als dass es funktioniert.

PSS Ich habe die Logik in mehrere Teile aufgeteilt und dieser behandelt nur das Akzeptieren von HTTP-Headern. Verarbeitet sie nicht.


Siehe, vor vielen Jahren habe ich einen Thread-Server mit blockierenden E / A geschrieben, weil es einfach zu schreiben war. Ein Kollege hat die andere Art geschrieben, und es hat bewundernswert funktioniert. Dies waren zwei Formen des Hauptproduktangebots in einem Unternehmen, bei dem ich früher gearbeitet habe. Für "faule Clients" im Blockierungsszenario können Sie eine Zeitüberschreitung beim Datenempfang haben.

Antworten:


4

Es gibt keine Silberkugel

In der Praxis kommt es darauf an ...

tl; dr - einfache Lösung, verwenden Sie Nginx ...

Blockierung:

Beispielsweise verwendet Apache standardmäßig ein Blockierungsschema, bei dem der Prozess für jede Verbindung verzweigt wird. Das bedeutet, dass jede Verbindung ihren eigenen Speicherplatz benötigt und der Aufwand für die Kontextumschaltung mit zunehmender Anzahl von Verbindungen zunimmt. Der Vorteil ist jedoch, dass nach dem Schließen einer Verbindung der Kontext entsorgt werden kann und der gesamte Speicher leicht abgerufen werden kann.

Ein Multithread-Ansatz wäre insofern ähnlich, als der Overhead der Kontextumschaltung mit der Anzahl der Verbindungen zunimmt, in einem gemeinsam genutzten Kontext jedoch speichereffizienter sein kann. Das Problem bei einem solchen Ansatz ist, dass es schwierig ist, den gemeinsam genutzten Speicher auf sichere Weise zu verwalten. Die Ansätze zur Überwindung von Speichersynchronisationsproblemen umfassen häufig ihren eigenen Overhead. Beispielsweise kann das Sperren des Hauptthreads bei CPU-intensiven Lasten einfrieren, und die Verwendung unveränderlicher Typen führt zu einer Menge unnötigem Kopieren von Daten.

AFAIK verwendet im Allgemeinen einen Multiprozess-Ansatz auf einem blockierenden HTTP-Server, da es sicherer / einfacher ist, Speicher auf sichere Weise zu verwalten / wiederherzustellen. Die Speicherbereinigung wird zu einem Problem, wenn die Wiederherstellung des Speichers so einfach ist wie das Stoppen eines Prozesses. Für lang laufende Prozesse (dh einen Daemon) ist diese Eigenschaft besonders wichtig.

Während der Overhead für die Kontextumschaltung bei einer kleinen Anzahl von Mitarbeitern unbedeutend erscheint, werden die Nachteile relevanter, wenn die Last auf Hunderte bis Tausende von gleichzeitigen Verbindungen skaliert. Im besten Fall skaliert die Kontextumschaltung O (n) auf die Anzahl der anwesenden Arbeitnehmer, in der Praxis ist dies jedoch höchstwahrscheinlich schlimmer.

Wenn Server, die das Blockieren verwenden, möglicherweise nicht die ideale Wahl für schwere E / A-Lasten sind, sind sie ideal für CPU-intensive Arbeit, und die Nachrichtenübermittlung wird auf ein Minimum beschränkt.

Nicht blockierend:

Nicht blockierend wäre so etwas wie Node.js oder Nginx. Diese sind insbesondere für die Skalierung auf eine viel größere Anzahl von Verbindungen pro Knoten unter E / A-intensiver Last bekannt. Sobald die Leute die Obergrenze der Thread- / prozessbasierten Server erreicht hatten, begannen sie im Grunde, nach alternativen Optionen zu suchen. Dies wird auch als C10K-Problem bezeichnet (dh die Fähigkeit, 10.000 gleichzeitige Verbindungen zu verarbeiten).

Nicht blockierende asynchrone Server haben im Allgemeinen viele Merkmale mit einem Multi-Threaded-with-Locking-Ansatz gemeinsam, da Sie vorsichtig sein müssen, um CPU-intensive Lasten zu vermeiden, da Sie den Haupt-Thread nicht überlasten möchten. Der Vorteil besteht darin, dass der durch die Kontextumschaltung entstehende Overhead im Wesentlichen entfällt und mit nur einer Kontextnachricht die Weitergabe kein Problem darstellt.

Während es für viele Netzwerkprotokolle möglicherweise nicht funktioniert, funktioniert die zustandslose Natur von HTTPs besonders gut für nicht blockierende Architekturen. Durch die Kombination eines Reverse-Proxys und mehrerer nicht blockierender HTTP-Server können Knoten mit hoher Auslastung identifiziert und umgeleitet werden.

Selbst auf einem Server mit nur einem Knoten enthält das Setup häufig einen Server pro Prozessorkern, um den Durchsatz zu maximieren.

Beide:

Der "ideale" Anwendungsfall wäre eine Kombination aus beiden. Ein Reverse-Proxy an der Vorderseite für Routing-Anforderungen an der Spitze, dann eine Mischung aus blockierenden und nicht blockierenden Servern. Nicht blockierend für E / A-Aufgaben wie das Bereitstellen von statischem Inhalt, Cache-Inhalt und HTML-Inhalt. Blockieren für CPU-schwere Aufgaben wie das Codieren von Bildern / Videos, das Streamen von Inhalten, das Knacken von Zahlen, das Schreiben von Datenbanken usw.

In Ihrem Fall:

Wenn Sie nur Header überprüfen, aber die Anforderungen nicht tatsächlich verarbeiten, beschreiben Sie im Wesentlichen einen Reverse-Proxy. In einem solchen Fall würde ich definitiv einen asynchronen Ansatz wählen.

Ich würde vorschlagen, die Dokumentation für den in Nginx integrierten Reverse-Proxy zu lesen .

Beiseite:

Ich habe den Artikel über den von Ihnen angegebenen Link gelesen und es ist sinnvoll, dass Async eine schlechte Wahl für die jeweilige Implementierung war. Das Problem kann in einer Aussage zusammengefasst werden.

Beim Wechseln zwischen Clients wurde festgestellt, dass der Code zum Speichern und Wiederherstellen von Werten / Status schwierig war

Sie bauten eine staatliche Plattform. In einem solchen Fall würde ein asynchroner Ansatz bedeuten, dass Sie den Status jedes Mal, wenn der Kontext wechselt (dh wenn ein Ereignis ausgelöst wird), ständig speichern / laden müssen. Darüber hinaus leisten sie auf der SMTP-Seite viel CPU-intensive Arbeit.

Es hört sich so an, als hätten sie Async ziemlich schlecht verstanden und infolgedessen viele schlechte Annahmen getroffen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.