node.js, mongodb, redis, bei Ubuntu-Leistungsabfall in der Produktion, RAM ist frei, CPU 100%

11

Wie der Fragentitel andeutet, fällt es mir schwer herauszufinden, was an meiner Anwendung verbessert (oder im Betriebssystem Ubuntu optimiert) werden kann, um eine akzeptable Leistung zu erzielen. Aber zuerst erkläre ich die Architektur:

Der Front-End-Server ist ein 8-Core-Computer mit 8 GB RAM unter Ubuntu 12.04. Die Anwendung ist vollständig in Javascript geschrieben und wird in node.js v 0.8.22 ausgeführt (da einige Module sich über neuere Versionen von Node zu beschweren scheinen). Ich verwende Nginx 1.4, um den HTTP-Verkehr von Port 80 und 443 auf 8 verwaltete Node Worker zu übertragen und begann mit der Verwendung der Node Cluster API. Ich verwende die neueste Version von socket.io 0.9.14, um die Websocket-Verbindungen zu verarbeiten, für die ich nur Websockets und Xhr-Polling als verfügbare Transporte aktiviert habe. Auf diesem Computer führe ich auch eine Instanz von Redis (2.2) aus.

Ich speichere persistente Daten (wie Benutzer und Scores) auf einem zweiten Server auf Mongodb (3.6) mit 4 GB RAM und 2 Kernen.

Die App ist seit einigen Monaten in Produktion (sie wurde bis vor einigen Wochen auf einer einzigen Box ausgeführt) und wird von rund 18.000 Benutzern pro Tag verwendet. Abgesehen von einem Hauptproblem hat es immer sehr gut funktioniert: Leistungsabfall. Mit der Verwendung wächst die Menge an CPU, die von jedem Prozess verwendet wird, bis der Worker staturiert wird (was keine Anforderungen mehr erfüllt). Ich habe es vorübergehend gelöst, indem ich die von jedem Mitarbeiter verwendete CPU jede Minute überprüfe und neu starte, wenn sie 98% erreicht. Das Problem hier ist also hauptsächlich die CPU und nicht der Arbeitsspeicher. Der Arbeitsspeicher ist kein Problem mehr, da ich auf socket.io 0.9.14 aktualisiert habe (die frühere Version hat Speicher verloren), daher bezweifle ich, dass es sich um ein Problem mit Speicherverlusten handelt, insbesondere weil jetzt die CPU ziemlich schnell wächst ( Ich muss jeden Arbeiter ungefähr 10-12 mal am Tag neu starten!). Der verwendete Arbeitsspeicher wächst ebenfalls, um ehrlich zu sein. aber sehr langsam, 1 Gig alle 2-3 Tage, und das Seltsame ist, dass es nicht freigegeben wird, selbst wenn ich die gesamte Anwendung komplett neu starte. Es wird nur freigegeben, wenn ich den Server neu starte! das kann ich nicht wirklich verstehen ...

Ich habe jetzt Nodefly entdeckt, was erstaunlich ist, sodass ich endlich sehen kann, was auf meinem Produktionsserver passiert, und ich sammle seit ein paar Tagen Daten. Wenn jemand die Diagramme sehen möchte, kann ich Ihnen Zugriff gewähren, aber im Grunde kann ich sehen, dass ich zwischen 80 und 200 gleichzeitige Verbindungen habe! Ich hatte erwartet, dass node.js Tausende und nicht Hunderte von Anfragen verarbeitet. Auch die durchschnittliche Antwortzeit für http-Verkehr liegt zwischen 500 und 1500 Millisekunden, was meiner Meinung nach sehr viel ist. In diesem Moment, in dem 1300 Benutzer online sind, ist dies die Ausgabe von "ss -s":

Total: 5013 (kernel 5533)
TCP:   8047 (estab 4788, closed 3097, orphaned 139, synrecv 0, timewait 3097/0), ports 0

Transport Total     IP        IPv6
*         5533      -         -
RAW       0         0         0
UDP       0         0         0
TCP       4950      4948      2
INET      4950      4948      2
FRAG      0         0         0

Das zeigt, dass ich in Timewait viele geschlossene Verbindungen habe. Ich habe die maximale Anzahl offener Dateien auf 999999 erhöht. Hier ist die Ausgabe von ulimit -a:

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 63724
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 999999
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 63724
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

Daher dachte ich, das Problem könnte im http-Verkehr liegen, der aus bestimmten Gründen die verfügbaren Ports / Sockets (?) Sättigt, aber eines macht für mich keinen Sinn: Warum, wenn ich die Worker neu starte und alle Clients innerhalb weniger Sekunden wieder eine Verbindung herstellen? Die Belastung der CPU des Arbeitnehmers sinkt auf 1% und kann Anforderungen ordnungsgemäß bearbeiten, bis sie nach etwa 1 Stunde (zur Spitzenzeit) gesättigt ist.

Ich bin hauptsächlich ein Javascript-Programmierer, kein Systemadministrator, daher weiß ich nicht, wie viel Last ich mit meinen Servern erwarten soll, aber es funktioniert sicherlich nicht so, wie es sollte. Die Anwendung ist ansonsten stabil und dieses letzte Problem hindert mich daran, die mobilen Versionen der App zu versenden, die fertig sind, da sie offensichtlich mehr Last bringen und schließlich das Ganze zum Absturz bringen werden!

Hoffentlich gibt es etwas Offensichtliches, das ich falsch mache, und jemand wird helfen, es zu erkennen ... Sie können mich gerne um weitere Informationen bitten, und es tut mir leid für die Länge der Frage, aber ich glaube, es war notwendig ... Danke im Voraus!

Franjanko
quelle
Gibt es eine Möglichkeit, so etwas wie einen Thread-Dump von node.js zu erhalten? Es gibt wahrscheinlich einige Threads in einer Endlosschleife. Was verwendet eigentlich CPU? Was sehen Sie, topwenn die CPU-Auslastung nahezu 100% beträgt?
rvs
Die CPU wird vollständig von nodejs verwendet. Wenn ich oben starte, sehe ich, dass die Knotenprozesse die gesamte CPU belegen. Ich bin mir nicht sicher, wie ich einen Thread-Dump vom Knoten ausgeben kann, um ehrlich zu sein ...
Franjanko
Eine andere Sache zu zeigen ist, dass der Großteil der CPU-Zeit auf das System zu gehen scheint, nicht auf die Benutzerzeit
Franjanko
Weiß jemand zumindest, wie viele gleichzeitige Verbindungen ich mit den Servern verarbeiten kann, die ich eingerichtet habe? Im Moment unterstütze ich max. 200 gleichzeitige Verbindungen. Dies hilft mir zu schätzen, wie weit ich von einer optimalen Konfiguration entfernt bin ... danke.
Franjanko

Antworten:

10

Nach einigen Tagen intensiver Versuche und Irrtümer bin ich froh, sagen zu können, dass ich verstanden habe, wo der Engpass war, und ich werde ihn hier veröffentlichen, damit andere Menschen von meinen Erkenntnissen profitieren können.

Das Problem liegt in den Pub / Sub-Verbindungen, die ich mit socket.io verwendet habe, und insbesondere im RedisStore, der von socket.io für die prozessübergreifende Kommunikation von Socket-Instanzen verwendet wird.

Nachdem mir klar wurde, dass ich meine eigene Version von pub / sub mithilfe von redis problemlos implementieren kann, habe ich beschlossen, es auszuprobieren, und den redisStore aus socket.io entfernt, wobei der Standardspeicher beibehalten wurde (ich muss nicht senden an alle verbundenen Clients, aber nur zwischen 2 verschiedenen Benutzern, die möglicherweise über verschiedene Prozesse verbunden sind)

Anfangs habe ich nur 2 globale Redis-Verbindungen x für die Verarbeitung des Pubs / Sub auf jedem verbundenen Client deklariert, und die Anwendung verwendete weniger Ressourcen, aber ich war immer noch von einem konstanten Wachstum der CPU-Auslastung betroffen, sodass sich nicht viel geändert hatte. Aber dann habe ich beschlossen, zwei neue Verbindungen zu erstellen, um Redis für jeden Client neu zu erstellen, damit sein Pub / Sub nur in seinen Sitzungen verwaltet wird, und dann die Verbindungen zu schließen, sobald der Benutzer die Verbindung getrennt hat. Dann, nach einem Tag in der Produktion, waren die CPUs immer noch bei 0-5% ... Bingo! Kein Prozess startet neu, keine Fehler, mit der Leistung, die ich erwartet hatte. Jetzt kann ich sagen, dass node.js rockt und bin froh, dass ich es für die Erstellung dieser App ausgewählt habe.

Glücklicherweise wurde redis so konzipiert, dass es viele gleichzeitige Verbindungen verarbeiten kann (anders als bei Mongo). Standardmäßig ist es auf 10.000 festgelegt, sodass auf einer einzelnen Redis-Instanz Platz für etwa 5.000 gleichzeitige Benutzer bleibt, was für den Moment für mich ausreicht, aber ich ' Ich habe gelesen, dass es auf bis zu 64.000 gleichzeitige Verbindungen übertragen werden kann, daher sollte diese Architektur meiner Meinung nach solide genug sein.

Zu diesem Zeitpunkt dachte ich darüber nach, eine Art Verbindungspool für Redis zu implementieren, um ihn ein wenig weiter zu optimieren, bin mir aber nicht sicher, ob dies nicht dazu führen wird, dass sich die Pub / Sub-Ereignisse erneut auf den Verbindungen aufbauen, es sei denn, jeder von ihnen wird jedes Mal zerstört und neu erstellt, um sie zu reinigen.

Wie auch immer, danke für Ihre Antworten, und ich bin gespannt, was Sie denken und ob Sie einen anderen Vorschlag haben.

Prost.

Franjanko
quelle
2
Ich habe das gleiche Problem in meiner Produktions-App, das auch für die Serveradministratorrolle neu ist. Ich folge dem, was Sie im Konzept getan haben, aber ich habe einige Fragen dazu - vielleicht könnten Sie in Ihrer akzeptierten Antwort einen Link zu einer Ressource bereitstellen? Oder einfach mehr Informationen geben? Insbesondere zu "Aber dann habe ich beschlossen, zwei neue Verbindungen zu erstellen, um für jeden Client Redis zu erstellen, damit sein Pub / Sub nur in seinen Sitzungen verwaltet wird, und dann die Verbindungen zu schließen, sobald der Benutzer die Verbindung getrennt hat."
toblerpwn
2

Haben Sie einen Quellcode zum Speichern? Es können Verbindungen zur Datenbank nicht geschlossen sein? Prozesse, die auf HTTP-Verbindungen warten, die niemals geschlossen werden.

Können Sie einige Protokolle posten?

Mach ein ps -ef und stelle sicher, dass noch nichts läuft. Ich habe gesehen, dass Webprozesse Zombies hinterlassen, die erst sterben, wenn Sie einen Kill -9 ausführen. Manchmal funktioniert das Herunterfahren nicht oder nicht vollständig und diese Threads oder Prozesse enthalten RAM und manchmal CPU.

Es kann sich um eine Endlosschleife irgendwo im Code handeln oder um einen abgestürzten Prozess, der auf einer Datenbankverbindung gehalten wird.

Welche NPM-Module verwenden? Sind sie alle die neuesten?

Fangen Sie Ausnahmen? Siehe: http://geoff.greer.fm/2012/06/10/nodejs-dealing-with-errors/ Siehe: /programming/10122245/capture-node-js-crash-reason

Allgemeine Hinweise:

http://clock.co.uk/tech-blogs/preventing-http-raise-hangup-error-on-destroyed-socket-write-from-crashing-your-nodejs-server

http://blog.nodejitsu.com/keep-a-nodejs-server-up-with-forever

http://hectorcorrea.com/blog/running-a-node-js-web-site-in-production-a-beginners-guide

/programming/1911015/how-to-debug-node-js-applications

https://github.com/dannycoates/node-inspector

http://elegantcode.com/2011/01/14/taking-baby-steps-with-node-js-debugging-with-node-inspector/

Tim Spann
quelle
1

Keine Antwort an sich, da Ihre Frage eher eine Geschichte als eine Frage mit einer Antwort ist.

Nur um zu sagen, dass ich erfolgreich einen node.js-Server mit socket.io erstellt habe, der über 1 Million dauerhafte Verbindungen mit einer durchschnittlichen Nachrichtennutzlast von 700 Bytes verarbeitet.

Die Netzwerkschnittstellenkarte mit 1 Gbit / s war zu Beginn überlastet, und ich sah eine Menge E / A-Wartezeiten von Veröffentlichungsereignissen für alle Clients.

Das Entfernen von Nginx aus der Proxy-Rolle hatte ebenfalls wertvollen Speicher zurückgegeben, da das Erreichen einer Million dauerhafter Verbindungen mit nur EINEM Server eine schwierige Aufgabe ist, Konfigurationen, Anwendungen zu optimieren und Betriebssystemparameter zu optimieren. Denken Sie daran, dass dies nur mit viel RAM möglich ist (ca. 1 Million Websockets-Verbindungen verbrauchen etwa 16 GB RAM. Mit node.js ist die Verwendung von sock.js meiner Meinung nach ideal für einen geringen Speicherverbrauch, aber vorerst für socket.io verbraucht so viel).

Dieser Link war mein Ausgangspunkt, um dieses Volumen an Verbindungen mit dem Knoten zu erreichen. Abgesehen davon, dass es sich um eine Erlang-App handelt, ist die gesamte Betriebssystemoptimierung ziemlich anwendungsunabhängig und sollte von jedem verwendet werden, der auf viele dauerhafte Verbindungen abzielt (Websockets oder Long-Polling).

HTH,

Marcel
quelle