Webanwendungen werden heute ĂŒberall eingesetzt, und HTTP macht dabei den Löwenanteil unter allen Transportprotokollen aus. Bei der Untersuchung der Nuancen der Webanwendungsentwicklung schenken die meisten der zugrunde liegenden Betriebssystemumgebung, in der diese Anwendungen tatsĂ€chlich betrieben werden, nur wenig Aufmerksamkeit. Die Trennung zwischen Entwicklung (Dev) und Betrieb (Ops) hat die Situation nur verschĂ€rft. Mit dem Aufstieg der DevOps-Kultur beginnen Entwickler jedoch, die Verantwortung fĂŒr den Betrieb ihrer Anwendungen in der Cloud zu ĂŒbernehmen, weshalb es fĂŒr sie sehr hilfreich ist, das Backend des Betriebssystems grĂŒndlich zu verstehen. Dies ist besonders vorteilhaft, wenn Sie versuchen, ein System fĂŒr Tausende oder Zehntausende gleichzeitiger Verbindungen bereitzustellen.
EinschrĂ€nkungen in Webdiensten Ă€hneln stark den EinschrĂ€nkungen in anderen Anwendungen. Egal, ob es sich um Lastenausgleichsmechanismen oder Datenbankserver handelt, all diese Anwendungen haben Ă€hnliche Probleme in einer leistungsstarken Umgebung. Das VerstĂ€ndnis dieser grundlegenden EinschrĂ€nkungen und Strategien zu deren Ăberwindung ermöglicht eine bessere EinschĂ€tzung der Leistung und Skalierbarkeit Ihrer Webanwendungen.
Ich schreibe diese Artikelreihe als Antwort auf die Fragen junger Entwickler, die gut informierte Systemarchitekten werden möchten. Es ist unmöglich, die Methoden zur Optimierung von Linux-Anwendungen klar zu verstehen, ohne sich mit den Grundlagen auseinander zu setzen, wie sie auf Betriebssystemebene funktionieren. Obwohl es viele Arten von Anwendungen gibt, möchte ich in diesem Zyklus Netzwerkanwendungen und nicht Desktop-Anwendungen wie Browser oder Texteditoren untersuchen. Dieses Material richtet sich an Entwickler und Architekten, die verstehen möchten, wie Programme unter Linux oder Unix funktionieren und wie man sie fĂŒr hohe Leistung strukturiert.
Linux ist ein serverbetriebssystem , und Ihre Anwendungen laufen hÀufig genau auf diesem Betriebssystem. Obwohl ich "Linux" sage, können Sie die meiste Zeit mit Sicherheit annehmen, dass alle Unix-Àhnlichen Betriebssysteme im Allgemeinen gemeint sind. Dennoch habe ich den begleitenden Code nicht auf anderen Systemen getestet. Wenn ich also etwas Linux-spezifisches ausprobiere, weise ich darauf hin.
Obwohl Sie das erworbene Wissen nutzen können, um eine Anwendung von Grund auf neu zu erstellen, die hervorragend optimiert ist, ist es besser, dies zu vermeiden. Wenn Sie einen neuen Webserver in C oder C++ fĂŒr die GeschĂ€ftsanwendung Ihrer Organisation schreiben, könnte dies Ihr letzter Arbeitstag sein. Das Wissen ĂŒber die Struktur dieser Anwendungen hilft jedoch bei der Auswahl bereits existierender Programme. Sie können Systeme auf Basis von Prozessen mit Systemen auf Basis von Threads sowie auf Basis von Ereignissen vergleichen. Sie werden verstehen und schĂ€tzen, warum Nginx besser funktioniert als Apache httpd und warum eine Python-Anwendung auf Basis von Tornado mehr Benutzer bedienen kann als eine Python-Anwendung auf Basis von Django.
ZeroHTTPd: Lernwerkzeug
 â ein Webserver, den ich von Grund auf in C als Lernwerkzeug geschrieben habe. Er hat keine externen AbhĂ€ngigkeiten, einschlieĂlich des Zugriffs auf Redis. Wir fĂŒhren unsere eigenen Redis-Prozeduren aus. Weitere Informationen finden Sie weiter unten.
Obwohl wir die Theorie lange diskutieren könnten, gibt es nichts Besseres, als Code zu schreiben, ihn auszufĂŒhren und alle Serverarchitekturen miteinander zu vergleichen. Dies ist die anschaulichste Methode. Deshalb werden wir einen einfachen Webserver ZeroHTTPd schreiben und jedes Modell anwenden: auf Basis von Prozessen, Threads und Ereignissen. Wir werden jeden dieser Server testen und sehen, wie sie im Vergleich zueinander funktionieren. ZeroHTTPd wird in einer einzigen C-Datei implementiert. Der Ereignis-basierte Server enthĂ€lt , eine hervorragende Implementierung einer Hash-Tabelle, die in einer einzigen Header-Datei geliefert wird. In den anderen FĂ€llen gibt es keine AbhĂ€ngigkeiten, um das Projekt nicht zu komplizieren.
Der Code enthĂ€lt viele Kommentare, um beim VerstĂ€ndnis zu helfen. Als einfacher Webserver in wenigen Codezeilen stellt ZeroHTTPd auch ein minimales Framework fĂŒr die Webentwicklung dar. Es hat eine begrenzte FunktionalitĂ€t, kann jedoch statische Dateien ausliefern und sehr einfache âdynamischeâ Seiten erstellen. Ich muss sagen, dass ZeroHTTPd gut geeignet ist, um zu lernen, wie man leistungsstarke Linux-Anwendungen erstellt. Im Grunde genommen warten die meisten Webdienste auf Anfragen, ĂŒberprĂŒfen sie und verarbeiten sie. Genau das wird ZeroHTTPd tun. Es ist ein Lernwerkzeug und nicht fĂŒr den produktiven Einsatz gedacht. Es hat keine starken FĂ€higkeiten zur Fehlerbehandlung und wird wahrscheinlich nicht mit den besten Sicherheitspraktiken prahlen (oh ja, ich habe verwendet strcpy) oder mit abstrusen Tricks der Sprache C. Aber ich hoffe, dass er seine Aufgabe gut meistern wird.

Startseite von ZeroHTTPd. Er kann verschiedene Dateitypen ausgeben, einschlieĂlich Bilder
Anwendung fĂŒr ein GĂ€stebuch
Moderne Webanwendungen sind in der Regel nicht auf statische Dateien beschrĂ€nkt. Sie haben komplexe Interaktionen mit verschiedenen Datenbanken, Caches usw. Daher werden wir eine einfache Webanwendung namens âGĂ€stebuchâ erstellen, in der Besucher EintrĂ€ge unter ihren Namen hinterlassen. Das GĂ€stebuch speichert zuvor hinterlassene EintrĂ€ge. AuĂerdem gibt es einen ZĂ€hler fĂŒr die Besucher am unteren Rand der Seite.

Webanwendung âGĂ€stebuchâ ZeroHTTPd
Der BesucherzĂ€hler und die EintrĂ€ge des GĂ€stebuchs werden in Redis gespeichert. FĂŒr die Kommunikation mit Redis wurden eigene Prozeduren implementiert, die nicht von externen Bibliotheken abhĂ€ngen. Ich bin kein groĂer Fan davon, eigenen Code zu entwickeln, wenn es öffentliche und gut getestete Lösungen gibt. Aber das Ziel von ZeroHTTPd ist es, die Leistung von Linux und den Zugriff auf externe Dienste zu studieren, wĂ€hrend die Bearbeitung von HTTP-Anfragen die Leistung erheblich beeinflusst. Wir mĂŒssen die Kommunikation mit Redis in jeder unserer Serverarchitekturen vollstĂ€ndig kontrollieren. In einer Architektur verwenden wir blockierende Aufrufe, in anderen prozedurbasierte Ereignisse. Die Verwendung einer externen Client-Bibliothek fĂŒr Redis wĂŒrde diese Kontrolle nicht ermöglichen. Zudem fĂŒhrt unser kleiner Redis-Client nur einige Funktionen aus (Abruf, Einstellung und Erhöhung eines SchlĂŒssels; Abruf und HinzufĂŒgen zu einem Array). DarĂŒber hinaus ist das Redis-Protokoll Ă€uĂerst elegant und einfach. Man muss es nicht einmal speziell lernen. Die Tatsache, dass die gesamte Arbeit des Protokolls in etwa einhundert Zeilen Code erledigt wird, zeigt, wie gut es durchdacht ist.
Im folgenden Bild sind die VorgÀnge der Anwendung dargestellt, wenn der Client (Browser) anfragt /guestbookURL.

Funktionsweise der GĂ€stebuchanwendung
Wenn eine Seite des GĂ€stebuchs ausgegeben werden soll, erfolgt ein Aufruf des Dateisystems zum Lesen der Vorlage in den Speicher und drei Netzwerkaufrufe an Redis. Die Vorlagendatei enthĂ€lt den GroĂteil des HTML-Inhalts fĂŒr die Seite auf dem obigen Screenshot. Dort gibt es auch spezielle Platzhalter fĂŒr die dynamischen Inhalte: die EintrĂ€ge und den BesucherzĂ€hler. Wir erhalten diese aus Redis, fĂŒgen sie in die Seite ein und liefern den vollstĂ€ndig formatierten Inhalt an den Client aus. Den dritten Redis-Abruf kann man vermeiden, da Redis den neuen Wert des SchlĂŒssels beim Erhöhen zurĂŒckgibt. FĂŒr unseren Server mit einer ereignisgesteuerten asynchronen Architektur ist jedoch eine Vielzahl von Netzwerkaufrufen eine gute Ăbung zu Lernzwecken. Daher ignorieren wir den von Redis zurĂŒckgegebenen Wert der Besucheranzahl und fordern ihn in einem separaten Aufruf an.
Serverarchitekturen von ZeroHTTPd
Wir erstellen sieben Versionen von ZeroHTTPd mit derselben FunktionalitÀt, jedoch mit unterschiedlichen Architekturen:
- Iterativ
- Fork-Server (ein Kindprozess pro Anfrage)
- Pre-Fork-Server (vorheriges Forking von Prozessen)
- Server mit AusfĂŒhrungsthreads (ein Thread pro Anfrage)
- Server mit vorab erstellten Threads
- Architektur auf Basis von
poll() - Architektur auf Basis von
epoll
Wir messen die Leistung jeder Architektur, indem wir den Server mit HTTP-Anfragen belasten. Bei Vergleichen von Architekturen mit einem hohen Grad an ParallelitÀt steigt die Anzahl der Anfragen. Wir testen dreimal und berechnen den Durchschnitt.
Testmethodologie

Setup fĂŒr Lasttests von ZeroHTTPd
Es ist wichtig, dass wĂ€hrend der Tests nicht alle Komponenten auf einer Maschine laufen. In diesem Fall bringt das Betriebssystem zusĂ€tzliche Overheadkosten fĂŒr die Planung mit sich, da die Komponenten um die CPU konkurrieren. Die Messung der Ăberheadkosten des Betriebssystems fĂŒr jede der gewĂ€hlten Serverarchitekturen ist eines der wichtigsten Ziele dieser Ăbung. Das HinzufĂŒgen weiterer Variablen wird den Prozess beeintrĂ€chtigen. Daher funktioniert die obige Konfiguration am besten.
Was jeder dieser Server macht
- load.unixism.net: hier fĂŒhren wir aus
ab, das Apache Benchmark-Tool. Es erzeugt die Last, die erforderlich ist, um unsere Serverarchitekturen zu testen. - nginx.unixism.net: Manchmal möchten wir mehr als eine Instanz eines Serverprogramms ausfĂŒhren. DafĂŒr arbeitet der Nginx-Server mit den entsprechenden Einstellungen als Lastverteiler fĂŒr ab unsere Serverprozesse.
- zerohttpd.unixism.net: Hier fĂŒhren wir unsere Serverprogramme auf sieben verschiedenen Architekturen aus, jeweils einzeln.
- redis.unixism.net: Auf diesem Server lÀuft der Redis-Daemon, wo die EintrÀge im GÀstebuch und der ZÀhler der Besucher gespeichert werden.
Alle Server laufen auf einem einzelnen Prozessor-Kern. Die Idee ist, die maximale Leistung jeder Architektur zu bewerten. Da alle Serverprogramme auf derselben Hardware getestet werden, ist dies die Basislinie fĂŒr ihren Vergleich. Meine Testumgebung besteht aus virtuellen Servern, die bei Digital Ocean gemietet werden.
Was messen wir?
Es können verschiedene Kennzahlen gemessen werden. Wir bewerten die Leistung jeder Architektur in dieser Konfiguration, indem wir die Server mit Anfragen auf unterschiedlichen Parallelisierungsstufen belasten: die Last steigt von 20 bis 15.000 gleichzeitigen Benutzern.
Testergebnisse
Im nÀchsten Diagramm wird die Leistung der Server auf verschiedenen Architekturen bei unterschiedlichen Parallelisierungsstufen gezeigt. Auf der y-Achse steht die Anzahl der Anfragen pro Sekunde, auf der x-Achse die parallelen Verbindungen.



Unten ist eine Tabelle mit den Ergebnissen.
Anfragen pro Sekunde
Parallelismus
iterativ
Fork
pre-fork
threaded
pre-threaded
poll
epoll
20
7
112
2100
1800
2250
1900
2050
50
7
190
2200
1700
2200
2000
2000
100
7
245
2200
1700
2200
2150
2100
200
7
330
2300
1750
2300
2200
2100
300
â
380
2200
1800
2400
2250
2150
400
â
410
2200
1750
2600
2000
2000
500
â
440
2300
1850
2700
1900
2212
600
â
460
2400
1800
2500
1700
2519
700
â
460
2400
1600
2490
1550
2607
800
â
460
2400
1600
2540
1400
2553
900
â
460
2300
1600
2472
1200
2567
1000
â
475
2300
1700
2485
1150
2439
1500
â
490
2400
1550
2620
900
2479
2000
â
350
2400
1400
2396
550
2200
2500
â
280
2100
1300
2453
490
2262
3000
â
280
1900
1250
2502
groĂe Streuung
2138
5000
â
groĂe Streuung
1600
1100
2519
â
2235
8000
â
â
1200
groĂe Streuung
2451
â
2100
10Â 000
â
â
groĂe Streuung
â
2200
â
2200
11Â 000
â
â
â
â
2200
â
2122
12Â 000
â
â
â
â
970
â
1958
13Â 000
â
â
â
â
730
â
1897
14Â 000
â
â
â
â
590
â
1466
15Â 000
â
â
â
â
532
â
1281
Aus dem Diagramm und der Tabelle ist ersichtlich, dass bei ĂŒber 8000 gleichzeitigen Anfragen nur noch zwei Akteure ĂŒbrig bleiben: pre-fork und epoll. Mit zunehmender Last funktioniert der serverbasierte Poll schlechter als der Threaded. Die Architektur mit der vorzeitigen Erstellung von Threads stellt einen ernsthaften Wettbewerb fĂŒr epoll dar: Das ist ein Zeichen dafĂŒr, wie gut der Linux-Kernel eine groĂe Anzahl von Threads plant.
Quellcode von ZeroHTTPd
Quellcode von ZeroHTTPd . FĂŒr jede Architektur gibt es ein separates Verzeichnis.
ZeroHTTPd
â
âââ 01_iterativ
â âââ main.c
âââ 02_forking
â âââ main.c
âââ 03_pre-forking
â âââ main.c
âââ 04_threading
â âââ main.c
âââ 05_pre-threading
â âââ main.c
âââ 06_poll
â âââ main.c
âââ 07_epoll
â âââ main.c
âââ Makefile
âââ public
â âââ index.html
â âââ tux.png
âââ templates
âââ guestbook
âââ index.htmlNeben sieben Verzeichnissen fĂŒr alle Architekturen gibt es im Stammverzeichnis noch zwei weitere: public und templates. Im ersten liegt die Datei index.html und ein Bild vom ersten Screenshot. Dort können andere Dateien und Ordner platziert werden, und ZeroHTTPd sollte diese statischen Dateien ohne Probleme bereitstellen. Wenn der Pfad im Browser mit dem Pfad im public-Ordner ĂŒbereinstimmt, sucht ZeroHTTPd nach der Datei index.html in diesem Verzeichnis. Der Inhalt fĂŒr das GĂ€stebuch wird dynamisch generiert. Es gibt nur die Hauptseite, deren Inhalt auf der Datei 'templates/guestbook/index.html' basiert. In ZeroHTTPd können dynamische Seiten problemlos hinzugefĂŒgt werden. Die Idee ist, dass Benutzer in diesem Verzeichnis Vorlagen hinzufĂŒgen und ZeroHTTPd nach Bedarf erweitern können.
Um alle sieben Server zu erstellen, starten Sie make all aus dem Stammverzeichnis â und alle Builds werden in diesem Verzeichnis erscheinen. AusfĂŒhrbare Dateien suchen die Verzeichnisse public und templates in dem Verzeichnis, aus dem sie gestartet werden.
Linux-API
Um die Informationen in diesem Artikelzyklus zu verstehen, mĂŒssen Sie sich nicht gut mit der Linux-API auskennen. Ich empfehle jedoch, mehr zu diesem Thema zu lesen, da es im Internet viele Informationsressourcen gibt. Obwohl wir einige Kategorien der Linux-API behandeln werden, werden wir uns hauptsĂ€chlich auf Prozesse, Threads, Ereignisse und den Netzwerk-Stack konzentrieren. Neben BĂŒchern und Artikeln ĂŒber die Linux-API empfehle ich auch, die Man-Seiten fĂŒr Systemaufrufe und verwendete Bibliotheksfunktionen zu lesen.
Leistung und Skalierbarkeit
Eine Anmerkung zur Leistung und Skalierbarkeit. Theoretisch gibt es keinen direkten Zusammenhang zwischen ihnen. Sie können einen Webdienst haben, der sehr gut funktioniert und eine Reaktionszeit von wenigen Millisekunden hat, der jedoch ĂŒberhaupt nicht skalierbar ist. Ebenso kann es eine schlecht funktionierende Webanwendung geben, die mehrere Sekunden fĂŒr eine Antwort benötigt, die jedoch auf Dutzende skalierbar ist, um Zehntausende gleichzeitiger Benutzer zu verarbeiten. Dennoch ist die Kombination aus hoher Leistung und Skalierbarkeit eine sehr starke Kombination. Hochleistungsanwendungen nutzen im Allgemeinen die Ressourcen effizient und bedienen so mehr gleichzeitige Benutzer auf dem Server, wĂ€hrend die Kosten gesenkt werden.
CPU- und I/O-Aufgaben
SchlieĂlich gibt es bei Berechnungen immer zwei mögliche Arten von Aufgaben: fĂŒr I/O und CPU. Das Abrufen von Anfragen ĂŒber das Internet (Netzwerkeingabe- und -ausgabe), die Verwaltung von Dateien (Netzwerk- und Festplatteneingabe- und -ausgabe) sowie die Kommunikation mit einer Datenbank (Netzwerk- und Festplatteneingabe- und -ausgabe) sind alles I/O-Aktionen. Einige Datenbankanfragen können die CPU etwas belasten (z. B. Sortieren, Berechnung des Durchschnitts von einer Million Ergebnissen usw.). Die meisten Webanwendungen sind hinsichtlich des maximal möglichen I/O begrenzt, wĂ€hrend der Prozessor selten voll ausgelastet ist. Wenn Sie sehen, dass bei einer bestimmten I/O-Aufgabe viel CPU verwendet wird, ist das wahrscheinlich ein Zeichen fĂŒr eine schlechte Anwendungsarchitektur. Das kann bedeuten, dass CPU-Ressourcen fĂŒr das Prozessmanagement und das Kontextwechseln aufgewendet werden â und das ist nicht besonders nĂŒtzlich. Wenn Sie etwas wie Bildverarbeitung, Audiokonvertierung oder maschinelles Lernen durchfĂŒhren, dann benötigt die Anwendung leistungsstarke CPU-Ressourcen. FĂŒr die meisten Anwendungen trifft das jedoch nicht zu.
Mehr ĂŒber Serverarchitekturen
Quelle: habr.com
