kostenloser Webspace werbefrei: lima-city


Best Practice: Gut skalierbarer Webserver in nativer Sprache

lima-cityForumProgrammiersprachenSonstige Programmiersprachen

  1. Autor dieses Themas

    g****e

    Moin,

    ich bin eigentlich seit Jahren Javascript Entwickler, und ich LIEBE diese Sprache. Es gibt nichts was man nicht machen kann (dank NodeJS), es ist super einfach, und die Asynchronität ist ein so wunderbares Modell, ich liebe es wirklich sehr. Mit Javascript fällt mir eigentlich alles sehr einfach, und ich habe alles lösen können, aber ich möchte dennoch auch mit anderen Sprachen arbeiten und meinen Horizont erweitern.

    Im Moment steht Rust auf dem Plan, dies könnte aber auch ohne weiteres C oder C++ sein in dieser Frage, das gibt sich in meinen Augen nichts.
    Unzwar möchte ich einen WebServer schreiben, der eigentlich nur Anfragen entgegen nimmt, daraufhin Anfragen an eine Datenbank stellt, und die Antwort dann wieder zurück schickt. Das ist in Javascript eine Sache von ~5min, hier ist das Problem von Skalierbarkeit aber auch schon für mich gelöst.
    Stattdessen überlege ich nun, wie mache ich das eigentlich selbst? Also wirklich von unten auf?

    Der erste Gedanke war einfach für jede Anfrage einen Thread zu spawnen. Dadurch könnte ich jede Anfrage in einen Raum packen, wo eine Datenbankanfrage auch mal seine Zeit dauern kann, und es stört keine andere Anfrage. Das hat aber den krassen Nachteil, dass wenn ich 10k+ gleichzeitige Anfragen habe, ich 10k+ Threads habe, und ich glaube das find ein einfacher kleiner Server nicht lustig. Zwar würde ich wohl alle CPU Kerne nutzen, aber das wäre dennoch nicht optimal glaube ich.

    Der zweite Gedanke war an Go zu denken, wo es diese tollen Goroutinen gibt. Warum also nicht mit Coroutinen für jede Anfrage? Das sollte doch besser laufen! Aber... Ich habe schon sooo oft gelesen, dass die Leute in Go genau damit Probleme bekommen haben: 10k+ Coroutinen sind nach dem was ich weiß ebenfalls nicht so toll, und eher ineffizient.

    Wenn ich die Anfragen in einem Prozess nach und nach, einen nach dem anderen, abarbeite ist das ebenfalls nicht wirklich effizient, da der Prozess ja permanent active-polling zum lesen und schreiben machen müsste. Heißt, ich arbeite mit viel warten und blocken drin...

    Aber warte, das ist genau das was NodeJS löst -> Non-Blocking, Single-Process Workload. Heißt, ich müsste eigentlich genau wie in NodeJS eine Eventloop aufsetzen, welche genau wie libuv stream-handles mit einem Event-System verarbeitet, das vom Betriebssystem gesteuert wird (also mittels Betriebssystem Notifiern, wann bestimmte Ressourcen zur Verfügung stehen).
    Und hier hapere ich wieder: In der Node-Welt sind die APIs bereits so für mich gestrickt, dass ich Promises oder Callbacks bekomme, in der normalen Driver-Welt jedoch nicht. Hier habe ich jedoch einen Verbindungspool, über welchen ich Anfragen starte...? Sind diese Verbindungspools dafür da, dass ich über viele Threads hin weg jeweils eine eigene Verbindung verwenden kann, um eine 1zu1 Beziehung zwischen Anfrage -> Verbindung habe?
    Heißt aber, dass, solange ich eine Anfrage am laufen habe, eigentlich mein Thread steht und wartet, oder sehe ich das falsch? Das will ich ja gar nicht, eigentlich. Also... Bin ich ein bisschen verwirrt.

    Ich könnte jetzt anfangen mich so weit zu versuchen, dass ich Datenbank anfragen ebenfalls in einzelne Threads über einen Thread-Pool auslagere, und diese dann Thread-Übergreifend Signale austauschen um Callbacks in einer zentralen Event-Loop mit entsprechenden Ergebnissen zu verarbeiten.... Oder... Bin ich auf dem Holzweg? Weil eigentlich würde ich jetzt ja nur NodeJS nachimplementieren, nur in einer nativen Sprache.

    Was ist also normaler best practice für einen WebServer, der groß skalieren kann, in einer nativen Sprache?

    Ziel für mich ist es, das zu verstehen, und selbst einmal zu implementieren. Dabei soll meine Implementierung mit einer vergleichbaren NodeJS Implementierung mithalten können in Speed, also >10k aktive (lesend und schreibende) Verbindungen gleichzeitig halten, bedienen und das mit einer möglichst geringen Latenz. Effizienz ist mir wichtig^^

    Ich freue mich auf Input =) :prost:
  2. Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!

    lima-city: Gratis werbefreier Webspace für deine eigene Homepage

  3. hackyourlife

    Moderator Kostenloser Webspace von hackyourlife

    hackyourlife hat kostenlosen Webspace.

    Rust kann und kenne ich nicht, aber mit C / C++ machst du dir wohl direkt viel Aufwand … ich würde mir das erst mal mit Java bauen. Performance-Mäßig solltest du da immer noch Nodejs übertreffen können, aber es ist viel einfacher umzusetzen.

    Threads pro Anfrage spawnen: das tut man nicht. Nicht (nur) deshalb, weil es dann zu viele Threads gibt, sondern vor allem auch weil das Anlegen eines Threads viel Zeit braucht. Normalerweise baut man stattdessen einen Pool mit Worker-Threads, denen man dann Arbeit zuteilt. Du hast dann eine Queue, in die du Arbeit reingibst, und Worker-Threads, die nichts zu tun haben, holen sich dort Arbeit ab. Vorteil davon: es gibt nur begrenzt viele Threads, und es müssen keine Threads neu angelegt werden, während das Verteilen der Arbeit viel weniger Zeit braucht als Threads anzulegen.

    Du musst auch nicht zwingend Verbindungen an Threads binden, sondern kannst stattdessen Dinge wie select nutzen, um in einem Hauptthread darauf zu warten, dass sich irgendwas bei irgend einer Verbindung tut. Du kannst dann beispielsweise wenn sich bei einer Verbindung etwas tut, das wieder als Arbeitspaket in den Arbeitspool einstellen und von einem Worker abarbeiten lassen. Hier musst du nur darauf aufpassen, dass der Hauptthread nicht zum Bottleneck wird, und du auch nicht zu viele viel zu kleine Arbeitspakete generierst.

    Du kannst nun zu solchen Verbindungen weitere Daten (mit einer eigenen Datenstruktur) zuordnen, beispielsweise die Aktion, die als nächstes passieren soll, oder die dazugehörige Datenbankverbindung. Bei diesem Programmiermodell muss dir aber klar sein, dass Arbeitspakete möglichst nicht allzu groß sein sollten, da du sonst zu einem Punkt kommen kannst, an dem alle Workerthreads für längere Zeit beschäftigt sind und neue Arbeit sehr lange warten muss.

    In C/C++ müsstest du dich nun darum kümmern, mit pthreads o.ä. die Worker-Threads zu bauen, deine Arbeitspool-Struktur threadsicher zu gestalten, den Threadpool verwalten (und auch sauberes Terminieren erlauben), Sockets mit select überwachen usw. Warum ich dir da Java vorgeschlagen habe: weil du das dort wesentlich einfacher korrekt umsetzen kannst, ohne allzu viel Performance zu verlieren, und Dinge wie Speicherfehler usw nahezu ausgeschlossen sind. Wenn du das probehalber wirklich in Java baust: nimm nicht die im JDK enthaltenen ExecutorService-Klassen, denn laut Messungen hat sich gezeigt, dass die arg langsam beim Verteilen von Arbeit sind. Und außerdem kannst du dir somit auch das, was du in C bräuchtest, selbst bauen.

    Wenn du das wirklich in C/C++ bauen willst:: in C/C++ ist das genau gleich umzusetzen, aber mit durchaus mehr Aufwand verbunden, dass am Ende auch wirklich keine Lecks, Speicherüberläufe, Deadlocks o.ä. entstehen, und Performance-Mäßig wird das wohl nicht allzu viel schneller sein (auch wenn mich da ein Benchmark natürlich interessieren würde).

    Das mit »Nodejs nachimplementieren« meint wohl: Eine Umgebung bauen, um nach einem ähnlichen Programmiermodell zu programmieren, wie bei Nodejs, oder? Falls ja: das ist wohl richtig. Aber ist das jetzt schlecht? Es heißt übrigens auch noch lange nicht, dass Nodejs an sich wirklich schnell/performant ist, es heißt lediglich, dass durch das Programmiermodell bedingt hohe Performance erreichbar ist. Aber das ist auch mit anderen Managed-Sprachen möglich, genauso wie mit nativen Sprachen. Probier es einfach aus, und bau das mal testweise z.B. in C und in Java, und vergleich dann die Performance.

    Insgesamt würde ich mir übrigens derzeit mit dem Wissen, dass sich gerade bei Technologie wie der JVM u.ä. viel tut, nicht allzu viel auf C/C++/… geben, und für Web-Dinge einfach Java/Nodejs/Python/Ruby/… nutzen, also einfach das, was am einfachsten zu nutzen ist ;-)
  4. Autor dieses Themas

    g****e

    Hey, danke

    Ich habe C/C++ nur erwähnt, weil Rust recht ähnlich ist. Du solltest es dir mal anschauen, ist wirklich cool. Total low-level, aber durch die Konzepte dahinter ist es eine Herausforderung fehlerhaften Code zu schreiben (also logisch falschen kannst du immer schreiben, aber mit Memory-Leaks, Data-Races, Thread-Inkonsistenzen, Null-Pointern oder so). https://www.rust-lang.org/
    Die erste Weile ist man irre verwirrt wegen dem Compiler, weil er einfach alles irgendwie anmeckert, aber er hat zum glück ein --explain dabei, sodass für jeden Fehler Beispiele und Korrekturvorschläge direkt im Terminal dabei sind, ohne erst zu googln.

    Dass das viel Aufwand machen kann ist mir erstmal egal^^ Es geht sowieso in die Hose befürchte ich, aber ich möchte daraus lehren ziehen. Und dann möcht ich das gleich einmal richtig machen. Vor allem weil das alles in meinem Kopf so wenig Sinn gemacht hat.

    Ok, damit bestätigst du aber mein Bild, welches ich mir gestern Nacht auch überlegt hatte, welches am effizientesten sein sollte.

    Danke, du hast mir sehr geholfen =)
  5. Rust ist definitiv eine Sprache, die man sich mal angeschaut haben sollte. Momentan sehr starkes Wachstum und erstklassige Community.

    Aber zurück zum eigentlichen Thema: Performance ist ein sehr facettenreiches Thema. Und solange du nicht ein Team von Leuten hast, die sich mit ihrem Wissen gut ergänzen, wirst du wahrscheinlich nicht mal ansatzweise an die Geschwindigkeit von Node.js rankommen. Aber das ist okay, weil du ja in erster Line etwas lernen willst. Deswegen folgender Vorschlag: Schau dir den Quellcode von Node.js an. Er ist OpenSource und vermutlich sogar gut dokumentiert.

    Alternativ kannst du dir auch nginx anschauen, welches ebenfalls mit dem Ziel entwickelt wurde hochgradig performant HTTP Requests zu bearbeiten. Laut Wikipedia kann er 10.000 Verbindungen gleichzeitig abhandeln bei geringem RAM Verbrauch.

    Und dann gibt es noch lighttpd welcher ebenfalls auf hohe Geschwindigkeiten ausgelegt ist.

    Natürlich ist es immer cool, ein eigenes Projekt aus dem Boden zu stampfen, aber es ist auch wichtig, dass man Dinge zuende bringt. Ein high-performance Webserver ist definitiv eine verdammt große Herausforderung, vor allem weil es dabei um viele dreckige technische Details wie Caching geht.

    Bezüglich Threads: Ich würde Threads jetzt nicht völlig ausschließen. Webserver wie der von der Apache Foundation nutzen Threads (bzw. Forks) um für eingehende Requests eigene Prozesse zu haben. Und die fahren recht gut damit. Aber für Webserver mit hohen Anfoderungen kann eine andere Strategie notwendig sein.

    Beitrag zuletzt geändert: 16.6.2016 17:45:49 von bladehunter
  6. Autor dieses Themas

    g****e

    Hey,

    viel zu tun, und viel gewälzt, daher erst so spät.

    Ich habe tatsächlich mal Sourcecode und Theorie-Modelle von Plattformen wie Nginx durchgeschaut, und auch bestehende Rust HTTP Projekte, die versprechen hohe Performance zu erreichen, aber auch viele Blogs von Startups, die erklären, wie sie kosten zu sparen, und dabei hab ich viel gelernt. Das wichtigste möchte ich hier mit euch noch teilen:

    Wie Hackyourlife schon ausgeführt hat, wird für alles, was irgendwie eine hohe Performance erreichen muss, ein Worker-Thread-Pool mit entsprechender Arbeitsaufteilung aufgesetzt. Davor wird eine Event-Queue gesetzt, welche mittels Kernel-Funktionen wie epoll oder kqueue dem Betriebssystem die Arbeit überlässt, zu benachrichten wenn was zu tun ist, und des wird dann an die Threads verteilt.
    Nginx arbeitet tatsächlich auch nach diesem asynchronen IO Prinzip, genau wie NodeJS, oder auch lighttpd (hier hab ich nur die Beschreibung gelesen, die beschreiben sich aber auch auf dem gleichen Prinzip basierend).

    Heißt, was ich beherrschen lernen muss, ist eine Event-Queue und einen Worker-Thread-Pool^^ Dafür gibts bereits Libs, die ich nu auch schon mal durchgeschaut habe, und es ist alles absolut logisch, und eigentlich sogar einfach, aber so ohne Übung würd ich das noch nicht zusammen bekommen, aaaaaaber es heißt ja auch Übung macht den Meister.

    Ich sage großen Dank für die Informationen, ihr habt mich bestärkt in den Gedanken und Ideen die ich hatte, und mir geholfen nochmal einen Schritt weiter zu kommen.
  7. Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!

    lima-city: Gratis werbefreier Webspace für deine eigene Homepage

Dir gefällt dieses Thema?

Über lima-city

Login zum Webhosting ohne Werbung!