Dieser Artikel ist der erste einer Reihe von A Node.JS Holiday Season-Artikeln, die Ihnen vom Identity-Team von Mozilla präsentiert wurden, das letzten Monat die erste Beta-Version von Persona veröffentlicht hat. Bei der Entwicklung von Persona haben wir eine Reihe von Tools entwickelt, vom Debuggen über die Lokalisierung bis hin zum Abhängigkeitsmanagement und mehr. In dieser Artikelserie werden wir unsere Erfahrungen und diese Tools mit der Community teilen, die für jeden nützlich sein werden, der mit Node.js einen Hochverfügbarkeitsdienst aufbauen möchte. Wir hoffen, dass Ihnen diese Artikel gefallen und freuen uns auf Ihre Gedanken und Beiträge.
Wir beginnen mit einem aktuellen Artikel über ein wesentliches Problem in Node.js: Speicherlecks. Wir stellen Node-Memwatch vor – eine Bibliothek, die dabei hilft, Speicherlecks in Node zu finden und zu isolieren.
Warum verlangst du Ärger?
Die am häufigsten gestellte Frage zum Verfolgen von Speicherlecks lautet: „Warum sollten Sie sich die Mühe machen?“ Gibt es nicht dringendere Probleme, die zuerst angegangen werden müssen? Warum entscheiden Sie sich nicht dafür, den Dienst von Zeit zu Zeit neu zu starten oder ihm mehr RAM zuzuweisen? Um diese Fragen zu beantworten, schlagen wir die folgenden drei Vorschläge vor:
1. Vielleicht interessiert Sie der wachsende Speicherbedarf nicht, aber V8 schon (V8 ist die Engine der Node-Laufzeit). Mit zunehmenden Speicherlecks geht V8 aggressiver mit dem Garbage Collector um, was dazu führen kann, dass Ihre Anwendung langsamer ausgeführt wird. Daher beeinträchtigen Speicherlecks auf dem Knoten die Programmleistung.
2. Speicherlecks können andere Arten von Fehlern auslösen. Code, der Speicher verliert, verweist möglicherweise ständig auf begrenzte Ressourcen. Es könnte sein, dass Ihnen die Dateideskriptoren ausgehen und Sie möglicherweise plötzlich keine neuen Datenbankverbindungen mehr herstellen können. Diese Art von Problem kann auftreten, lange bevor Ihrer App der Speicher ausgeht, aber es kann trotzdem zu Problemen führen.
3. Irgendwann wird Ihre App früher oder später abstürzen, und das wird auf jeden Fall passieren, wenn Ihre App immer beliebter wird. Auf Hacker News wird jeder über Sie lachen und sich lächerlich machen, was Sie zu einer Tragödie machen wird.
Wo ist das Ameisennest, das die Böschung von Tausenden von Kilometern durchbrochen hat?
Beim Erstellen komplexer Anwendungen kann es an vielen Stellen zu Speicherverlusten kommen. Schließungen sind wohl die bekanntesten und berüchtigtsten. Da Abschlüsse Verweise auf Dinge innerhalb ihres Gültigkeitsbereichs behalten, kommt es normalerweise zu Speicherlecks.
Verschlusslecks werden oft erst entdeckt, wenn jemand danach sucht. Aber in der asynchronen Welt von Node generieren wir jederzeit und überall ständig Abschlüsse durch Rückruffunktionen. Wenn diese Rückruffunktionen nicht unmittelbar nach der Erstellung verwendet werden, wächst der zugewiesene Speicher weiter und Code, der scheinbar keine Speicherlecks aufweist, tritt aus. Und ein solches Problem ist schwieriger zu finden.
Ihre Anwendung kann aufgrund von Problemen im Upstream-Code auch Speicherverluste verursachen. Vielleicht können Sie den Code finden, der den Speicherverlust verursacht hat, aber vielleicht starren Sie einfach nur auf Ihren perfekten Code und fragen sich, wie er verloren gegangen ist!
Es sind diese schwer zu lokalisierenden Speicherlecks, die uns dazu bringen, ein Tool wie Node-Memwatch zu wollen. Der Legende nach schloss sich unser eigener Lloyd Hilaiel vor ein paar Monaten zwei Tage lang in einem kleinen Raum ein, um einen Speicherverlust aufzuspüren, der bei Stresstests offensichtlich wurde. (Bleiben Sie übrigens gespannt auf Lloyds kommenden Artikel über Lasttests)
Nach zwei Tagen harter Arbeit entdeckte er schließlich den Übeltäter im Node-Kernel: Der Event-Listener in http.ClientRequest wurde nicht freigegeben. (Der Patch, der das Problem schließlich behob, bestand nur aus zwei, aber entscheidenden Buchstaben). Es war diese schmerzhafte Erfahrung, die Lloyd dazu veranlasste, ein Tool zu schreiben, das dabei helfen könnte, Speicherlecks zu finden.
Tool zum Auffinden von Speicherlecks
Es gibt viele nützliche und sich ständig verbessernde Tools zum Auffinden von Speicherlecks in Node.js-Anwendungen. Hier sind einige davon:
Wir alle mögen die oben genannten Tools, aber keines davon trifft auf unser Szenario zu. Web Inspector eignet sich hervorragend zum Entwickeln von Anwendungen, ist jedoch in Hot-Deployment-Szenarien schwierig zu verwenden, insbesondere wenn mehrere Server und Unterprozesse beteiligt sind. Ebenso sind Speicherlecks, die bei langfristigen Hochlastvorgängen auftreten, schwer zu reproduzieren. Tools wie dtrace und libumem sind zwar beeindruckend, aber nicht auf allen Betriebssystemen verfügbar.
Enternode-memwatch
Wir benötigen eine plattformübergreifende Debugging-Bibliothek, die nicht erfordert, dass das Gerät uns mitteilt, wann unser Programm möglicherweise einen Speicherverlust aufweist, und die uns dabei hilft, herauszufinden, wo der Verlust vorliegt. Also haben wir Node-Memwatch implementiert.
Es versorgt uns mit drei Dingen:
Ein „Leck“-Ereignissemitter
memwatch.on('leak', function(info) { // look at info to find out about what might be leaking });
Ein Statusereignis-Emitter
var memwatch = require('memwatch'); memwatch.on('stats', function(stats) { // do something with post-gc memory usage stats });
Eine Heap-Speicherbereichsklassifizierung
var hd = new memwatch.HeapDiff(); // your code here ... var diff = hd.end();
Und es gibt auch eine Funktion, die den Garbage Collector auslösen kann, was beim Testen sehr nützlich ist. Okay, insgesamt vier.
var stats = memwatch.gc();
memwatch.on('stats', ...): Post-GC-Heap-Statistik
node-memwatch kann nach einer vollständigen Speicherbereinigung und Speicherkomprimierung ein Beispiel für die Speichernutzung ausgeben, bevor ein JS-Objekt zugewiesen wird. (Es verwendet den Post-GC-Hook von V8, V8::AddGCEpilogueCallback, um jedes Mal, wenn eine Garbage Collection ausgelöst wird, Informationen zur Heap-Nutzung zu sammeln.)
Zu den Statistiken gehören:
Hier ist ein Beispiel dafür, wie die Daten für eine Anwendung mit einem Speicherverlust aussehen. Das folgende Diagramm zeigt die Speichernutzung im Zeitverlauf. Die verrückte grüne Linie zeigt, was Process.memoryUsage() meldet. Die rote Linie zeigt die von node_memwatch gemeldete current_base. Das Feld unten links zeigt zusätzliche Informationen.
Beachten Sie, dass die Incr-GCs sehr hoch sind. Das bedeutet, dass V8 verzweifelt versucht, den Speicher zu löschen.
memwatch.on('leak', ...): Heap-Zuteilungstrend
Wir haben einen einfachen Erkennungsalgorithmus definiert, um Sie darauf aufmerksam zu machen, dass Ihre Anwendung möglicherweise einen Speicherverlust aufweist. Das heißt, wenn nach fünf aufeinanderfolgenden GCs der Speicher immer noch zugewiesen, aber nicht freigegeben ist, gibt Node-Memwatch ein Leak-Ereignis aus. Das spezifische Informationsformat der Veranstaltung ist klar und leicht lesbar, etwa so:
{ start: Fri, 29 Jun 2012 14:12:13 GMT, end: Fri, 29 Jun 2012 14:12:33 GMT, growth: 67984, reason: 'heap growth over 5 consecutive GCs (20s) - 11.67 mb/hr' }
memwatch.HeapDiff(): 查找泄漏元凶
最后,node-memwatch能比较堆上对象的名称和分配数量的快照,其对比前后的差异可以帮助找出导致内存泄漏的元凶。
var hd = new memwatch.HeapDiff(); // Your code here ... var diff = hd.end();
对比产生的内容就像这样:
{ "before": { "nodes": 11625, "size_bytes": 1869904, "size": "1.78 mb" }, "after": { "nodes": 21435, "size_bytes": 2119136, "size": "2.02 mb" }, "change": { "size_bytes": 249232, "size": "243.39 kb", "freed_nodes": 197, "allocated_nodes": 10007, "details": [ { "what": "Array", "size_bytes": 66688, "size": "65.13 kb", "+": 4, "-": 78 }, { "what": "Code", "size_bytes": -55296, "size": "-54 kb", "+": 1, "-": 57 }, { "what": "LeakingClass", "size_bytes": 239952, "size": "234.33 kb", "+": 9998, "-": 0 }, { "what": "String", "size_bytes": -2120, "size": "-2.07 kb", "+": 3, "-": 62 } ] } }
HeapDiff方法在进行数据采样前会先进行一次完整的垃圾回收,以使得到的数据不会充满太多无用的信息。memwatch的事件处理会忽略掉由HeapDiff触发的垃圾回收事件,所以在stats事件的监听回调函数中你可以安全地调用HeapDiff方法。