Warum wurden asynchrone Webserver angezeigt?

Hallo alle zusammen. In Kontakt Vladislav Rodin. Ich bin derzeit Leiter des High Load Architect-Kurses bei OTUS und unterrichte auch Kurse über Softwarearchitektur.

Wie Sie sehen, habe ich nicht nur unterrichtet, sondern auch für ein Blog-Copyright-Material OTUS Habré geschrieben. Der heutige Artikel möchte mit dem Beginn des Kurses «Linux Administrator» zusammenfallen , der jetzt geöffnet ist.





Einführung


Warum verlangsamt sich die Webanwendung und hält die Last nicht? Die Entwickler, die als erste auf eine solche Frage stießen und einige Systeme erforschten, kamen zu dem enttäuschenden Ergebnis, dass die Optimierung einer Geschäftslogik allein nicht ausreichen würde. Die Antwort auf diese Frage liegt auf einer niedrigeren Ebene - auf der Ebene des Betriebssystems. Damit Ihre Anwendung die Last halten kann, muss das Architekturkonzept so überarbeitet werden, dass es auf dieser Ebene effektiv funktioniert. Dies führte zur Entstehung asynchroner Webserver.

Leider konnte ich kein einziges Material finden, mit dem ich alle kausalen Zusammenhänge in der Entwicklung von Webservern auf einmal wiederherstellen kann. So kam die Idee auf, diesen Artikel zu schreiben, der hoffentlich zu einem solchen Material wird.

Linux-Betriebssystemfunktionen


Bevor ich über die Modelle von Webservern spreche, erlaube ich mir, einige Funktionen der Prozesse und Threads unter Linux abzurufen. Wir werden dies benötigen, wenn wir die Vor- und Nachteile der oben genannten Modelle analysieren.

Kontextwechsel


Höchstwahrscheinlich wird jeder Benutzer, der weiß, dass nur ein Programm gleichzeitig auf einem Prozessorkern ausgeführt werden kann, fragen: "Warum können 20 Programme gleichzeitig auf meinem 4-Kern-Prozessor gestartet werden?".

Tatsächlich liegt dies an der Tatsache, dass präventives Multitasking stattfindet . Das Betriebssystem weist eine bestimmte Zeitspanne (~ 50 μs) zu und setzt das Programm während dieser Zeit auf dem Kernel aus. Nach Ablauf der Zeit wird der Kontextwechsel unterbrochen und umgeschaltet. Das heißt, das Betriebssystem setzt einfach das nächste auszuführende Programm. Da häufig gewechselt wird, haben wir den Eindruck, dass alle Programme gleichzeitig arbeiten. Achten Sie auf die hohe Schaltfrequenz, dies ist wichtig für die nachfolgende Präsentation.

Der Kontextwechsel wurde oben erwähnt. Was beinhaltet es? Beim Umschalten des Kontexts müssen die Prozessorregister gespeichert, die Befehlspipeline gelöscht und die dem Prozess zugewiesenen Speicherbereiche gespeichert werden. Im Allgemeinen ist die Operation ziemlich teuer. Es dauert ~ 0,5 μs, während die Ausführung einer einfachen Codezeile ~ 1 ns beträgt. Darüber hinaus steigt mit zunehmender Anzahl von Prozessen pro Prozessorkern der Overhead für die Kontextumschaltung.

Webserver-Modelle


Derzeit existieren die folgenden Webservermodelle:

  • Arbeiter
  • Vorgabel
  • asynchron
  • kombiniert


Lassen Sie uns jeden von ihnen einzeln diskutieren.

Arbeiter und Vorgabel


Historisch gesehen begann mit diesen Modellen alles. Das Wesentliche ist sehr einfach : Wenn ein Kunde zu uns kommt, wählen wir einen separaten Handler für ihn aus, der den eingehenden Kunden von Anfang bis Ende verarbeitet. Ein Handler kann entweder ein Prozess (Prefork) oder ein Thread (Worker) sein. Ein Beispiel für einen solchen Webserver ist der bekannte Apache.

Ich werde sofort eine Reservierung vornehmen: Das Erstellen eines neuen Handlers für jeden Kunden ist teuer. Erstens führt bei einer konstanten Anzahl von Kernen eine Erhöhung der Anzahl von Prozessoren zu einer Erhöhung der Latenz (aufgrund von Kontextwechseln). Zweitens wächst die erforderliche Speichermenge linear mit der Zunahme der Clients, da selbst wenn Sie Threads mit gemeinsamer Speicherung verwenden, jeder Thread seinen eigenen Stapel hat. Somit ist die Anzahl der gleichzeitig verarbeiteten Clients begrenzt.Die Größe des Pools hängt wiederum von der Anzahl der Prozessorkerne ab. Das Problem wird mit vertikalen Skalierungsmethoden gelöst.

Ein weiterer grundlegender Nachteil solcher Server ist die nicht optimale Nutzung der Ressourcen. Prozesse (oder Threads) sind die meiste Zeit inaktiv . Stellen Sie sich die folgende Situation vor: Während der Clientverarbeitung müssen einige Daten von der Festplatte abgerufen, eine Anforderung an die Datenbank gestellt oder etwas in das Netzwerk geschrieben werden. Da das Lesen von einer Festplatte unter Linux ein blockierender Vorgang ist , bleibt der Prozess (oder Thread) hängen und wartet auf eine Antwort, nimmt jedoch weiterhin an der Zuweisung der Prozessorzeit teil.

Arbeiter gegen Vorgabel


Arbeiter und Vorgabel haben einige grundlegende Unterschiede. Streams sind im Speicher etwas wirtschaftlicher, weil sie ihn gemeinsam nutzen. Aus dem gleichen Grund ist ein Kontextwechsel zwischen ihnen einfacher als zwischen Prozessen. Im Fall eines Workers wird der Code jedoch multithreaded, da die Threads synchronisiert werden müssen. Als Ergebnis erhalten wir alle „Reize“ von Multithread-Code: Es wird schwieriger, ihn zu schreiben, zu lesen, zu testen und zu debuggen.

Asynchrones Modell


Worker und Prefork erlauben es daher aufgrund der begrenzten Poolgröße nicht, eine große Anzahl von Clients gleichzeitig zu verarbeiten, und nutzen Ressourcen aufgrund von Kontextwechsel und Blockieren von Systemaufrufen nicht optimal. Wie Sie sehen können, ist das Problem Multithreading und ein schwerer OS-Scheduler. Dies führt zu der folgenden Idee: Lassen Sie uns Clients in nur einem Thread verarbeiten, aber zu 100% laden.

Solche Server basieren auf einer Ereignisschleife und einer Reaktorvorlage ( Ereignismaschine ). Der Client-Code, der eine E / A-Operation initiiert, registriert einen Rückrufin einer Prioritätswarteschlange (Priorität ist Bereitschaftszeit). Die Ereignisschleife fragt die auf E / A wartenden Deskriptoren ab und aktualisiert dann die Priorität (falls verfügbar). Darüber hinaus zieht die Ereignisschleife Ereignisse aus der Prioritätswarteschlange heraus, wodurch Rückrufe am Ende die Steuerung an die Ereignisschleife zurückgeben.

Mit diesem Modell können Sie eine große Anzahl von Clients verwalten und so den Overhead vermeiden, den Kontext zu wechseln . Dieses Modell ist nicht perfekt und hat mehrere Nachteile. Erstens wird nicht mehr als ein Prozessorkern verbraucht , da es nur einen Prozess gibt. Dies wird unter Verwendung eines kombinierten Modells behandelt, das unten diskutiert wird. Zweitens sind Kunden durch diesen Prozess verbunden.. Der Code muss sorgfältig geschrieben werden. Speicherlecks, Fehler führen dazu, dass alle Clients auf einmal abfallen. Darüber hinaus sollte dieser Prozess durch nichts blockiert werden. Ein Rückruf sollte nicht darin bestehen, einige schwierige Aufgaben zu lösen, da alle Clients blockiert werden. Drittens ist asynchroner Code viel komplizierter . Es ist notwendig, einen zusätzlichen Rückruf zu registrieren, damit die Daten nicht kommen, um das Problem der korrekten Verzweigung usw. zu lösen.

Kombiniertes Modell


Dieses Modell wird in realen Servern verwendet. Dieses Modell verfügt über einen Pool von Prozessen, von denen jeder über einen Pool von Threads verfügt, von denen jeder wiederum ein Verarbeitungsmodell verwendet, das auf asynchroner Eingabe / Ausgabe basiert. Nginx präsentiert ein kombiniertes Modell.

Fazit


Ausgehend von den Grundlagen des Betriebssystems haben wir die konzeptionellen Unterschiede zwischen den in Apache und Nginx verwendeten Webservermodellen untersucht. Jeder von ihnen hat seine eigenen Vor- und Nachteile, so dass ihre Kombination häufig in der Produktion verwendet wird.

Die Idee der asynchronen Verarbeitung hat sich weiterentwickelt: Auf der Ebene der Sprachplattformen entstanden die Konzepte der grünen Fäden / Fasern / Goroutinen, mit denen Sie die Asynchronität von Eingabe und Ausgabe "unter der Haube verstecken" können, sodass der Entwickler mit einem schönen synchronen Code zufrieden ist. Dieses Konzept verdient jedoch einen separaten Artikel.



Erfahren Sie mehr über den Kurs.



All Articles