Webanwendungen sind heutzutage weit verbreitet, und unter allen Transportprotokollen hat HTTP den gröĂten Anteil. Bei der Entwicklung von Webanwendungen schenken die meisten der zugrunde liegenden Betriebssystemumgebung wenig Beachtung. Die Trennung zwischen Entwicklung (Dev) und Betrieb (Ops) hat die Situation zusĂ€tzlich erschwert. Mit der Verbreitung der DevOps-Kultur ĂŒbernehmen Entwickler jedoch zunehmend Verantwortung fĂŒr das Betreiben ihrer Anwendungen in der Cloud, weshalb ein vertieftes VerstĂ€ndnis der Backend-Umgebung des Betriebssystems Ă€uĂerst vorteilhaft ist. Dies ist besonders wichtig, wenn Sie ein System fĂŒr tausende oder sogar zehntausende gleichzeitige Verbindungen bereitstellen möchten.
Die EinschrĂ€nkungen in Webdiensten Ă€hneln stark denen anderer Anwendungen. Ob Lastenausgleich oder Datenbankserver, diese Anwendungen haben in leistungsstarken Umgebungen Ă€hnliche Herausforderungen. Das VerstĂ€ndnis dieser grundlegenden EinschrĂ€nkungen und der Möglichkeiten zu ihrer Ăberwindung ermöglicht es insgesamt, die Leistung und Skalierbarkeit Ihrer Webanwendungen besser zu bewerten.
Ich schreibe diese Artikelreihe als Antwort auf die Fragen junger Entwickler, die gut informierte Systemarchitekten werden möchten. Man kann die Methoden zur Optimierung von Linux-Anwendungen nicht klar verstehen, ohne in die Grundlagen einzutauchen und zu verstehen, wie sie auf Betriebssystemebene funktionieren. Obwohl es viele Arten von Anwendungen gibt, möchte ich in diesem Zyklus Netzwerk-Anwendungen untersuchen und nicht Desktop-Anwendungen wie Browser oder Texteditoren. 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 höchste Leistung strukturiert.
Linux ist ein Server- Betriebssystem, und am hĂ€ufigsten laufen Ihre Anwendungen genau auf diesem OS. Obwohl ich âLinuxâ sage, können Sie die meiste Zeit mit Sicherheit annehmen, dass alle Unix-Ă€hnlichen Betriebssysteme im Allgemeinen gemeint sind. Ich habe den begleitenden Code allerdings nicht auf anderen Systemen getestet. Wenn Sie also an FreeBSD oder OpenBSD interessiert sind, können die Ergebnisse abweichen. Wenn ich etwas spezifisch fĂŒr Linux ausprobiere, mache ich darauf aufmerksam.
Obwohl Sie das Wissen nutzen können, um eine Anwendung von Grund auf neu zu erstellen, und diese hervorragend optimiert sein wird, ist es besser, dies zu vermeiden. Wenn Sie einen neuen Webserver in C oder C++ fĂŒr die GeschĂ€ftsapplication Ihrer Organisation schreiben, könnte dies Ihr letzter Arbeitstag sein. Allerdings wird Ihnen das VerstĂ€ndnis der Struktur dieser Anwendungen bei der Auswahl bereits existierender Software helfen. Sie können Systeme basierend auf Prozessen mit solchen auf Basis von Threads sowie Event-basierten Systemen vergleichen. Sie werden verstehen und nachvollziehen können, warum Nginx besser funktioniert als Apache httpd und warum eine Python-Anwendung auf Basis von Tornado mehr Benutzer bedienen kann als eine Python-Anwendung, die auf Django basiert.
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 s. unten.
Obwohl wir lange ĂŒber die Theorie diskutieren könnten, gibt es nichts Besseres, als Code zu schreiben, ihn auszufĂŒhren und die verschiedenen Serverarchitekturen miteinander zu vergleichen. Das ist die anschaulichste Methode. Daher werden wir einen einfachen Webserver namens ZeroHTTPd erstellen, indem wir jedes Modell anwenden: auf der Basis von Prozessen, Threads und Ereignissen. Wir ĂŒberprĂŒfen jeden dieser Server und sehen, wie sie im Vergleich zueinander funktionieren. ZeroHTTPd ist in einer einzigen C-Datei implementiert. Der ereignisbasierte Server umfasst , eine hervorragende Implementierung einer Hash-Tabelle, die in einer einzigen Header-Datei bereitgestellt wird. Ansonsten gibt es keine AbhĂ€ngigkeiten, um das Projekt nicht zu komplizieren.
Der Code enthĂ€lt zahlreiche Kommentare, die beim VerstĂ€ndnis helfen. ZeroHTTPd, als einfacher Webserver und minimaler Rahmen fĂŒr Webentwicklung, bietet grundsĂ€tzlich eine eingeschrĂ€nkte FunktionalitĂ€t, kann jedoch statische Dateien sowie sehr einfache 'dynamische' Seiten ausgeben. Ich muss sagen, dass ZeroHTTPd sich gut fĂŒr das Lernen eignet, wie man leistungsstarke Linux-Anwendungen erstellt. Im Grunde genommen warten die meisten Webdienste auf Anfragen, ĂŒberprĂŒfen diese und verarbeiten sie. Genau das wird ZeroHTTPd tun. Es ist ein Lernwerkzeug und kein Produktivsystem. Es ist nicht besonders gut im Fehlermanagement und kann kaum mit den besten Sicherheitspraktiken aufwarten (oh ja, ich habe strcpy verwendet) oder mit ausgeklĂŒgelten Tricks der Programmiersprache C. Aber ich hoffe, dass es seinen Zweck gut erfĂŒllt. strcpy) oder mit ausgeklĂŒgelten Tricks der Programmiersprache C. Aber ich hoffe, dass es seinen Zweck gut erfĂŒllt.

Startseite von ZeroHTTPd. Es kann verschiedene Dateitypen ausgeben, einschlieĂlich Bilder.
GĂ€stebuch-Anwendung
Moderne Webanwendungen beschrĂ€nken sich in der Regel nicht auf statische Dateien. Sie umfassen komplexe Interaktionen mit verschiedenen Datenbanken, Caches usw. Daher erstellen wir eine einfache Webanwendung mit dem Namen âGĂ€stebuchâ, in dem Besucher EintrĂ€ge unter ihrem Namen hinterlassen können. Im GĂ€stebuch werden die zuvor hinterlassenen EintrĂ€ge gespeichert. AuĂerdem gibt es einen BesucherzĂ€hler am unteren Rand der Seite.

Webanwendung âGĂ€stebuchâ ZeroHTTPd
Der BesucherzĂ€hler und das GĂ€stebuch werden in Redis gespeichert. FĂŒr die Kommunikation mit Redis haben wir eigene Verfahren implementiert, die unabhĂ€ngig von externen Bibliotheken sind. Ich bin kein groĂer Fan davon, selbstentwickelten Code einzufĂŒhren, wenn es öffentliche und gut getestete Lösungen gibt. Aber das Ziel von ZeroHTTPd ist, die Leistung von Linux und den Zugriff auf externe Dienste zu untersuchen, 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 ereignisbasierte Prozeduren. Die Verwendung einer externen Redis-Clientbibliothek wĂŒrde uns nicht diesen Kontrollgrad bieten. AuĂerdem fĂŒhrt unser kleiner Redis-Client nur eine Handvoll Funktionen aus (Abrufen, Setzen und Erhöhen eines SchlĂŒssels; Abrufen und HinzufĂŒgen zu einem Array). Zudem ist das Redis-Protokoll auĂergewöhnlich elegant und einfach. Man muss es nicht einmal speziell lernen. Die Tatsache, dass das gesamte Protokoll in etwa einhundert Zeilen Code implementiert ist, zeigt, wie durchdacht es ist.
Im nÀchsten Bild sind die VorgÀnge der Anwendung dargestellt, wenn ein Kunde (Browser) eine Anfrage stellt. /guestbookURL.

Funktionsweise der GĂ€stebuch-Anwendung
Wenn die GĂ€stebuch-Seite ausgegeben werden soll, erfolgt ein Aufruf an das Dateisystem, um die Vorlage in den Speicher zu laden, sowie drei Netzwerkaufrufe an Redis. Die Vorlagendatei enthĂ€lt den GroĂteil des HTML-Inhalts fĂŒr die Seite im obigen Screenshot. Es gibt auch spezielle Platzhalter fĂŒr den dynamischen Teil des Inhalts: EintrĂ€ge und die BesuchszĂ€hler. Wir holen diese von Redis, fĂŒgen sie auf der Seite ein und liefern dem Kunden den vollstĂ€ndig formatierten Inhalt. Der dritte Aufruf an Redis kann vermieden werden, da Redis beim Inkrementieren einen neuen Wert des SchlĂŒssels zurĂŒckgibt. Allerdings stellt eine Vielzahl von Netzwerkaufrufen fĂŒr unseren auf Ereignissen basierenden asynchronen Server eine gute Herausforderung zu Lernzwecken dar. Daher lassen wir den zurĂŒckgegebenen Wert von Redis ĂŒber die Anzahl der Besucher weg und fordern ihn in einem separaten Aufruf an.
Serverarchitekturen von ZeroHTTPd
Wir erstellen sieben Versionen von ZeroHTTPd mit identischer FunktionalitÀt, jedoch unterschiedlichen Architekturen:
- Iterativ
- Fork-Server (ein Kindprozess pro Anfrage)
- Pre-Fork-Server (vorbereitendes Forking der Prozesse)
- Server mit AusfĂŒhrungsströmen (ein Thread pro Anfrage)
- Server mit vorab erstellten Strömen
- Architektur basierend auf
poll() - Architektur basierend auf
epoll
Wir messen die Leistung jeder Architektur, indem wir den Server mit HTTP-Anfragen belasten. Bei den Vergleichen von Architekturen mit hohem Parallelismus steigt die Anzahl der Anfragen. Wir testen dreimal und berechnen den Durchschnitt.
Testmethodologie

Einrichtung fĂŒr Lasttests von ZeroHTTPd
Es ist wichtig, dass bei der DurchfĂŒhrung der Tests nicht alle Komponenten auf einer einzigen Maschine laufen. In diesem Fall ergeben sich zusĂ€tzliche Betriebssystemkosten durch das Scheduling, da die Komponenten um die CPU konkurrieren. Die Messung der Betriebssystemkosten mit jeder der ausgewĂ€hlten Serverarchitekturen ist eines der Hauptziele dieser Ăbung. Das HinzufĂŒgen weiterer Variablen kann den Prozess beeintrĂ€chtigen. Folglich funktioniert die oben gezeigte Konfiguration am besten.
Was jeder dieser Server tut
- load.unixism.net: hier starten wir
ab, das Tool Apache Benchmark. Es erzeugt die notwendige Last, um unsere Serverarchitekturen zu testen. - nginx.unixism.net: Manchmal möchten wir mehr als eine Instanz des Serverprogramms betreiben. Dazu fungiert der Nginx-Server mit entsprechenden Einstellungen als Lastverteiler fĂŒr die ankommenden Anfragen ab an unsere Serverprozesse.
- zerohttpd.unixism.net: Hier betreiben wir unsere Serverprogramme auf sieben verschiedenen Architekturen, allerdings jeweils nur eine zur selben Zeit.
- redis.unixism.net: Auf diesem Server lÀuft der Redis-Daemon, in dem die EintrÀge des GÀstebuchs sowie der BesucherzÀhler gespeichert werden.
Alle Server arbeiten auf einem einzelnen Prozessorkern. Die Idee dahinter ist, die maximale Leistung jeder Architektur zu bewerten. Da alle Serverprogramme auf derselben Hardware getestet werden, ergibt sich hier ein Grundniveau fĂŒr deren Vergleich. Mein Testaufbau besteht aus virtuellen Servern, die ich bei Digital Ocean angemietet habe.
Was messen wir?
Es können verschiedene Metriken gemessen werden. Wir bewerten die Leistung jeder Architektur in dieser Konfiguration, indem wir die Server mit Anfragen bei unterschiedlichen ParallelitÀtsstufen belasten: die Last steigt von 20 bis zu 15.000 gleichzeitigen Benutzern.
Testergebnisse
Das folgende Diagramm zeigt die Leistung von Servern auf verschiedenen Architekturen bei unterschiedlichen ParallelitĂ€tsstufen. Auf der y-Achse â Anfragen pro Sekunde, auf der x-Achse â parallele Verbindungen.



Nachfolgend eine Tabelle mit den Ergebnissen.
Anfragen pro Sekunde
ParallelitÀt
iterativ
Fork
Pre-Fork
Threading
Pre-Threading
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 geht hervor, dass bei mehr als 8000 gleichzeitigen Anfragen nur zwei Architekturen konkurrenzfĂ€hig bleiben: Pre-Fork und Epoll. Mit zunehmender Last schneidet der Poll-Server schlechter ab als der Threading-Server. Die Architektur mit vorab erstellten Threads konkurriert auf hohem Niveau mit Epoll: Das belegt, wie gut der Linux-Kernel eine groĂe Anzahl an Threads verwalten kann.
Der Quellcode von ZeroHTTPd
Der Quellcode von ZeroHTTPd . FĂŒr jede Architektur gibt es ein eigenes Verzeichnis.
ZeroHTTPd
â
âââ 01_iterative
â âââ main.c
âââ 02_forking
â âââ main.c
âââ 03_preforking
â âââ main.c
âââ 04_threading
â âââ main.c
âââ 05_prethreading
â âââ main.c
âââ 06_poll
â âââ main.c
âââ 07_epoll
â âââ main.c
âââ Makefile
âââ public
â âââ index.html
â âââ tux.png
âââ templates
âââ guestbook
âââ index.htmlNeben den sieben Verzeichnissen fĂŒr alle Architekturen gibt es im Hauptverzeichnis noch zwei weitere: public und templates. Im ersten liegt die Datei index.html sowie ein Bild vom ersten Screenshot. Dort können weitere Dateien und Ordner platziert werden, und ZeroHTTPd sollte diese statischen Dateien problemlos bereitstellen. Wenn der Pfad im Browser mit dem Pfad im public-Ordner ĂŒbereinstimmt, sucht ZeroHTTPd in diesem Verzeichnis nach der Datei index.html. 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. Dynamische Seiten lassen sich in ZeroHTTPd problemlos hinzufĂŒgen, sodass Benutzer in diesem Verzeichnis Templates hinzufĂŒgen und ZeroHTTPd bei Bedarf erweitern können.
Um alle sieben Server zu erstellen, starten Sie make all aus dem Hauptverzeichnis â und alle Builds erscheinen in diesem Verzeichnis. Die ausfĂŒhrbaren 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, ist es nicht notwendig, sich gut im Linux API auszukennen. Ich empfehle jedoch, mehr zu diesem Thema zu lesen, da es viele Ressourcen im Internet gibt. Obwohl wir einige Kategorien des Linux API ansprechen werden, liegt unser Fokus hauptsĂ€chlich auf Prozessen, Threads, Ereignissen und dem Netzwerk-Stack. Neben BĂŒchern und Artikeln ĂŒber das Linux API empfehle ich auch, die Man-Pages fĂŒr Systemaufrufe und verwendete Bibliotheksfunktionen zu lesen.
Leistung und Skalierbarkeit
Eine Anmerkung zur Performance und Skalierbarkeit. Theoretisch gibt es keinen direkten Zusammenhang zwischen den beiden. Sie können einen Webdienst haben, der sehr gut funktioniert, mit Reaktionszeiten von wenigen Millisekunden, aber nicht skalierbar ist. genauso kann es eine schlecht funktionierende Webanwendung geben, die mehrere Sekunden fĂŒr eine Antwort benötigt, aber in der Lage ist, auf Hunderte zu skalieren, um Zehntausende von gleichzeitigen Benutzern zu bedienen. Dennoch ist die Kombination aus hoher Performance und Skalierbarkeit eine sehr starke Kombination. Hochleistungsanwendungen nutzen im Allgemeinen Ressourcen effizient, was bedeutet, dass sie mehr gleichzeitige Benutzer auf einem Server bedienen können und so die Kosten senken.
CPU- und I/O-Aufgaben
Bei Berechnungen gibt es zwei grundlegende Arten von Aufgaben: I/O und CPU. Anfragen ĂŒber das Internet (Netzwerk-I/O), Dateiverwaltung (Netzwerk- und Festplattendatenverarbeitung) sowie Datenbankkommunikation (Netzwerk- und Festplattendatenverarbeitung) zĂ€hlen zu den I/O-AktivitĂ€ten. Einige Datenbankanfragen können die CPU geringfĂŒgig belasten (zum Beispiel Sortierung, Berechnung des Durchschnitts von Millionen von Ergebnissen usw.). Die meisten Webanwendungen sind in Bezug auf das maximal mögliche I/O eingeschrĂ€nkt, wĂ€hrend die CPU selten voll ausgelastet wird. Wenn Sie bei einer I/O-Aufgabe feststellen, dass viel CPU verwendet wird, deutet das oft auf eine ineffiziente Architektur der Anwendung hin. Dies kann darauf hindeuten, dass CPU-Ressourcen fĂŒr Prozessmanagement und Kontextwechsel verwendet werden â was nicht besonders vorteilhaft ist. Wenn Sie jedoch etwas wie Bildbearbeitung, Audioverarbeitung oder maschinelles Lernen durchfĂŒhren, benötigt die Anwendung leistungsstarke CPU-Ressourcen. Aber fĂŒr die meisten Anwendungen ist das nicht der Fall.
Weitere Informationen zu Serverarchitekturen
Quelle: habr.com
