Node.js und CPU-intensive Anforderungen

215

Ich habe angefangen, an Node.js HTTP-Server zu basteln und schreibe wirklich gerne serverseitiges Javascript, aber etwas hindert mich daran, Node.js für meine Webanwendung zu verwenden.

Ich verstehe das gesamte asynchrone E / A-Konzept, bin jedoch etwas besorgt über die Randfälle, in denen prozeduraler Code sehr CPU-intensiv ist, z. B. Bildmanipulation oder Sortieren großer Datenmengen.

Soweit ich weiß, ist der Server für einfache Webseitenanforderungen wie das Anzeigen einer Benutzerliste oder das Anzeigen eines Blogposts sehr schnell. Wenn ich jedoch sehr CPU-intensiven Code schreiben möchte (z. B. im Admin-Backend), der Grafiken generiert oder die Größe von Tausenden von Bildern ändert, ist die Anforderung sehr langsam (einige Sekunden). Da dieser Code nicht asynchron ist, werden alle Anforderungen, die während dieser wenigen Sekunden an den Server gesendet werden, blockiert, bis meine langsame Anforderung abgeschlossen ist.

Ein Vorschlag war, Web Worker für CPU-intensive Aufgaben zu verwenden. Ich befürchte jedoch, dass Web-Worker das Schreiben von sauberem Code erschweren werden, da dies durch das Einfügen einer separaten JS-Datei funktioniert. Was ist, wenn sich der CPU-intensive Code in der Methode eines Objekts befindet? Es ist schade, eine JS-Datei für jede Methode zu schreiben, die CPU-intensiv ist.

Ein weiterer Vorschlag war, einen untergeordneten Prozess zu erzeugen, aber das macht den Code noch weniger wartbar.

Irgendwelche Vorschläge, um dieses (wahrgenommene) Hindernis zu überwinden? Wie schreibt man mit Node.js sauberen objektorientierten Code, während sichergestellt wird, dass CPU-schwere Aufgaben asynchron ausgeführt werden?

Olivier Lalonde
quelle
2
Olivier, Sie haben die identische Frage gestellt, die ich mir vorgestellt hatte (neu im Knoten) und insbesondere in Bezug auf die Verarbeitung von Bildern. In Java kann ich einen ExecutorService mit festem Thread verwenden und ihm alle Größenänderungsjobs übergeben und warten, bis die gesamte Verbindung beendet ist. Im Knoten habe ich nicht herausgefunden, wie die Arbeit an ein externes Modul verschoben werden kann, das die Grenzen überschreitet (lassen Sie uns sagen wir) die maximale Anzahl gleichzeitiger Operationen auf 2 gleichzeitig. Haben Sie einen eleganten Weg gefunden, dies zu tun?
Riyad Kalla

Antworten:

55

Was Sie brauchen, ist eine Aufgabenwarteschlange! Es ist eine gute Sache, Ihre lang laufenden Aufgaben vom Webserver zu entfernen. Wenn Sie jede Aufgabe in einer "separaten" js-Datei aufbewahren, wird die Modularität und die Wiederverwendung von Code gefördert. Es zwingt Sie, darüber nachzudenken, wie Sie Ihr Programm so strukturieren können, dass es auf lange Sicht einfacher zu debuggen und zu warten ist. Ein weiterer Vorteil einer Aufgabenwarteschlange besteht darin, dass die Mitarbeiter in einer anderen Sprache geschrieben werden können. Stellen Sie einfach eine Aufgabe auf, erledigen Sie die Arbeit und schreiben Sie die Antwort zurück.

so etwas https://github.com/resque/resque

Hier ist ein Artikel von Github darüber, warum sie es gebaut haben http://github.com/blog/542-introducing-resque

Tim
quelle
35
Warum verlinken Sie in einer Frage, die speziell auf die Knotenwelt ausgerichtet ist, auf Ruby-Bibliotheken?
Jonathan Dumaine
1
@ JonathanDumaine Es ist eine gute Implementierung einer Task-Warteschlange. Rad den Ruby-Code und schreibe ihn in Javascript um. PROFITIEREN!
Simon Stender Boisen
2
Ich bin ein großer Fan von Gearman, die Gearman-Mitarbeiter fragen keinen Gearman-Server nach neuen Jobs ab - neue Jobs werden sofort an die Arbeiter weitergeleitet. Sehr
Casey Flynn
1
Tatsächlich hat es jemand in die Knotenwelt portiert: github.com/technoweenie/coffee-resque
FrontierPsycho
@ Pacerier, warum sagst du das? Was schlägst du vor?
Luis.espinal
289

Dies ist ein Missverständnis der Definition von Webserver - es sollte nur verwendet werden, um mit Clients zu "sprechen". Schwerlastaufgaben sollten an eigenständige Programme delegiert werden (dies kann natürlich auch in JS geschrieben werden).
Sie würden wahrscheinlich sagen, dass es schmutzig ist, aber ich versichere Ihnen, dass ein Webserverprozess, der beim Ändern der Bildgröße festsitzt, nur schlechter ist (selbst für Apache, wenn er andere Abfragen nicht blockiert). Sie können jedoch eine gemeinsame Bibliothek verwenden, um Code-Redundanz zu vermeiden.

EDIT: Ich habe mir eine Analogie ausgedacht; Webanwendung sollte als Restaurant sein. Sie haben Kellner (Webserver) und Köche (Arbeiter). Die Kellner stehen mit den Kunden in Kontakt und erledigen einfache Aufgaben wie das Bereitstellen eines Menüs oder das Erklären, ob ein Gericht vegetarisch ist. Zum anderen delegieren sie härtere Aufgaben an die Küche. Da die Kellner nur einfache Dinge tun, reagieren sie schnell und die Köche können sich auf ihre Arbeit konzentrieren.

Node.js hier wäre ein einzelner, aber sehr talentierter Kellner, der viele Anfragen gleichzeitig bearbeiten kann, und Apache wäre eine Gruppe dummer Kellner, die jeweils nur eine Anfrage bearbeiten. Wenn dieser Kellner von Node.j anfangen würde zu kochen, wäre dies eine unmittelbare Katastrophe. Dennoch könnte das Kochen auch einen großen Vorrat an Apache-Kellnern erschöpfen, ganz zu schweigen vom Chaos in der Küche und der fortschreitenden Abnahme der Reaktionsfähigkeit.

mbq
quelle
6
In einer Umgebung, in der Webserver über mehrere Threads oder Prozesse verfügen und mehr als eine gleichzeitige Anforderung verarbeiten können, ist es sehr häufig, dass einige Sekunden für eine einzelne Anforderung aufgewendet werden. Die Leute haben das erwartet. Ich würde sagen, dass das Missverständnis darin besteht, dass node.js ein "normaler" Webserver ist. Mit node.js müssen Sie Ihr Programmiermodell ein wenig anpassen, und dazu gehört auch, dass Sie "lang laufende" Arbeit an einen asynchronen Worker weitergeben.
Thilo
13
Spawnen Sie nicht für jede Anforderung einen untergeordneten Prozess (der den Zweck von node.js zunichte macht). Bringe Arbeiter nur aus deinen schweren Anfragen heraus. Oder leiten Sie Ihre umfangreiche Hintergrundarbeit an etwas anderes als node.js.
Thilo
47
Gute Analogie, mbq!
Lance Fisher
6
Ha, das gefällt mir wirklich gut. "Node.js: schlechte Praktiken schlecht funktionieren lassen"
Ethan
7
@mbq Ich mag die Analogie, aber es könnte etwas Arbeit gebrauchen. Das traditionelle Multithread-Modell wäre eine Person, die sowohl Kellner als auch Koch ist. Sobald die Bestellung eingegangen ist, muss diese Person zurückgehen und das Essen kochen, bevor sie eine andere Bestellung bearbeiten kann. Das node.js-Modell hat die Knoten als Kellner und die Webworker als Köche. Die Kellner kümmern sich um das Abrufen / Auflösen der Anforderungen, während die Mitarbeiter die zeitintensiveren Aufgaben erledigen. Wenn Sie eine größere Skalierung benötigen, machen Sie den Hauptserver einfach zu einem Knotencluster und übertragen die CPU-intensiven Aufgaben an andere Server, die für die Milti-Thread-Verarbeitung entwickelt wurden.
Evan Plaice
16

Sie möchten nicht, dass Ihr CPU-intensiver Code asynchron ausgeführt wird, sondern dass er parallel ausgeführt wird . Sie müssen die Verarbeitungsarbeit aus dem Thread herausholen, der HTTP-Anforderungen bedient. Nur so kann dieses Problem gelöst werden. Bei NodeJS lautet die Antwort das Cluster-Modul, zum Laichen von Kinderprozessen, um das schwere Heben zu erledigen. (AFAIK Node hat kein Konzept für Threads / Shared Memory; es sind Prozesse oder nichts). Sie haben zwei Möglichkeiten, wie Sie Ihre Anwendung strukturieren. Sie können die 80/20-Lösung erhalten, indem Sie 8 HTTP-Server erzeugen und rechenintensive Aufgaben synchron für die untergeordneten Prozesse ausführen. Das zu tun ist ziemlich einfach. Es kann eine Stunde dauern, bis Sie unter diesem Link darüber gelesen haben. Wenn Sie nur den Beispielcode oben auf diesem Link abreißen, erhalten Sie 95% des Weges dorthin.

Die andere Möglichkeit, dies zu strukturieren, besteht darin, eine Jobwarteschlange einzurichten und große Rechenaufgaben über die Warteschlange zu senden. Beachten Sie, dass mit dem IPC für eine Jobwarteschlange viel Overhead verbunden ist. Dies ist daher nur dann nützlich, wenn die Aufgaben erheblich größer sind als der Overhead.

Ich bin überrascht , dass keiner dieser anderen Antworten auch erwähnen Cluster.

Hintergrund: Asynchroner Code ist Code, der angehalten wird, bis irgendwo anders etwas passiert . An diesem Punkt wird der Code aktiviert und die Ausführung fortgesetzt. Ein sehr häufiger Fall, in dem irgendwo anders etwas Langsames passieren muss, ist E / A.

Asynchroner Code ist nicht nützlich, wenn Ihr Prozessor für die Arbeit verantwortlich ist. Genau das ist bei "rechenintensiven" Aufgaben der Fall.

Nun scheint es, dass asynchroner Code eine Nische ist, aber tatsächlich ist er sehr verbreitet. Es ist einfach nicht nützlich für rechenintensive Aufgaben.

Das Warten auf E / A ist ein Muster, das beispielsweise auf Webservern immer auftritt. Jeder Client, der eine Verbindung zu Ihrem Server herstellt, erhält einen Socket. Meistens sind die Steckdosen leer. Sie möchten nichts tun, bis ein Socket einige Daten empfängt. An diesem Punkt möchten Sie die Anforderung bearbeiten. Unter der Haube verwendet ein HTTP-Server wie Node eine Eventing-Bibliothek (libev), um die Tausenden offener Sockets zu verfolgen. Das Betriebssystem benachrichtigt libev, und libev benachrichtigt NodeJS, wenn einer der Sockets Daten abruft. Anschließend stellt NodeJS ein Ereignis in die Ereigniswarteschlange, und Ihr http-Code wird an dieser Stelle aktiviert und behandelt die Ereignisse nacheinander. Ereignisse werden erst in die Warteschlange gestellt, wenn der Socket einige Daten enthält, sodass Ereignisse niemals auf Daten warten - sie sind bereits für sie da.

Ereignisbasierte Webserver mit einem Thread sind als Paradigma sinnvoll, wenn der Engpass auf eine Reihe von meist leeren Socket-Verbindungen wartet und Sie nicht für jede inaktive Verbindung einen ganzen Thread oder Prozess benötigen und Ihre 250.000 nicht abfragen möchten Sockets, um den nächsten zu finden, der Daten enthält.

Maurer
quelle
sollte die richtige Antwort sein ... für eine Lösung, bei der Sie 8 Cluster erzeugen, benötigen Sie 8 Kerne, oder? Oder Load Balancer mit mehreren Servern.
Muhammad Umer
Auch was ist ein guter Weg, um mehr über die 2. Lösung zu erfahren, indem Sie eine Warteschlange einrichten. Das Konzept der Warteschlange ist ziemlich einfach, aber der Messaging-Teil zwischen Prozessen und der Warteschlange ist fremd.
Muhammad Umer
Das stimmt. Sie müssen die Arbeit irgendwie auf einen anderen Kern bringen. Dafür benötigen Sie einen anderen Kern.
Masonk
Re: Warteschlangen. Die praktische Antwort ist die Verwendung einer Jobwarteschlange. Es sind einige für Knoten verfügbar. Ich habe noch nie einen von ihnen verwendet, daher kann ich keine Empfehlung aussprechen. Die neugierige Antwort ist, dass Arbeitsprozesse und Warteschlangenprozesse letztendlich über Sockets kommunizieren werden.
Masonk
7

Einige Ansätze, die Sie verwenden können.

Wie @Tim feststellt, können Sie eine asynchrone Aufgabe erstellen, die sich außerhalb oder parallel zu Ihrer Hauptversorgungslogik befindet. Hängt von Ihren genauen Anforderungen ab, aber auch Cron kann als Warteschlangenmechanismus fungieren.

WebWorker können für Ihre asynchronen Prozesse arbeiten, werden jedoch derzeit von node.js nicht unterstützt. Es gibt einige Erweiterungen, die Unterstützung bieten, z. B. http://github.com/cramforce/node-worker

Sie erhalten weiterhin die Möglichkeit, Module und Code über den Standardmechanismus "Erforderlich" wiederzuverwenden. Sie müssen nur sicherstellen, dass beim ersten Versand an den Mitarbeiter alle Informationen übergeben werden, die zur Verarbeitung der Ergebnisse erforderlich sind.

Toby Hede
quelle
0

Verwendung child_processist eine Lösung. Aber jeder untergeordnete Prozess, der erzeugt wird, kann im Vergleich zu Go viel Speicher verbrauchengoroutines

Sie können auch eine warteschlangenbasierte Lösung wie kue verwenden

Neo
quelle