Die Leistung von Netzwerk-Anwendungen unter Linux. Einleitung

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

ZeroHTTPd — 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 uthash, 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.

Die Leistung von Netzwerk-Anwendungen unter Linux. Einleitung
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.

Die Leistung von Netzwerk-Anwendungen unter Linux. Einleitung
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.

Die Leistung von Netzwerk-Anwendungen unter Linux. Einleitung
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

Die Leistung von Netzwerk-Anwendungen unter Linux. Einleitung
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.

Die Leistung von Netzwerk-Anwendungen unter Linux. Einleitung

Die Leistung von Netzwerk-Anwendungen unter Linux. Einleitung

Die Leistung von Netzwerk-Anwendungen unter Linux. Einleitung

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 hier. 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.html

Neben 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

  1. Teil I. Iterative Architektur
  2. Teil II. Fork-Server
  3. Teil III. Pre-Fork-Server
  4. Teil IV. Server mit AusfĂŒhrungsstrĂ€ngen
  5. Teil V. Server mit vorab erzeugten StrÀngen
  6. Teil VI. Poll-basierte Architektur
  7. Teil VII. Epoll-basierte Architektur

Quelle: habr.com

60GB SSD 8Gb DDR4