VorwortSoftwareanwendungen werden im Hauptspeicher des Computers ausgeführt, der als Direktzugriffsspeicher (RAM) bezeichnet wird. JavaScript, insbesondere Nodejs (serverseitiges JS), ermöglicht es uns, kleine bis große Softwareprojekte für Endbenutzer zu schreiben. Der Umgang mit Programmspeicher ist immer eine heikle Angelegenheit, da eine fehlerhafte Implementierung alle anderen Anwendungen blockieren kann, die auf einem bestimmten Server oder System ausgeführt werden. C- und C++-Programmierer legen großen Wert auf die Speicherverwaltung, da sich in jeder Ecke des Codes schreckliche Speicherlecks verbergen. Aber haben Sie als JS-Entwickler sich wirklich um dieses Problem gekümmert? Da JS-Entwickler die Webserverprogrammierung normalerweise auf dedizierten Servern mit hoher Kapazität durchführen, bemerken sie Verzögerungen beim Multitasking möglicherweise nicht. Wenn wir beispielsweise einen Webserver entwickeln, werden wir auch mehrere Anwendungen ausführen, wie beispielsweise einen Datenbankserver (MySQL), einen Cache-Server (Redis) und je nach Bedarf weitere Anwendungen. Wir müssen uns darüber im Klaren sein, dass sie auch den verfügbaren Hauptspeicher verbrauchen. Wenn wir unsere Anwendung nachlässig schreiben, verschlechtern wir wahrscheinlich die Leistung anderer Prozesse oder verweigern ihnen sogar die Speicherzuweisung vollständig. In diesem Artikel lösen wir ein Problem, um NodeJS-Konstrukte wie Streams, Puffer und Pipes zu verstehen und zu sehen, wie jedes von ihnen das Schreiben speichereffizienter Anwendungen unterstützt. Problem: Kopieren großer DateienWenn jemand aufgefordert wird, ein Programm zum Kopieren von Dateien mit NodeJS zu schreiben, wird er schnell den folgenden Code schreiben: const fs = erfordern('fs'); let Dateiname = Prozess.argv[2]; let destPath = process.argv[3]; fs.readFile(Dateiname, (Fehler, Daten) => { wenn (err) throw err; fs.writeFile(Zielpfad || 'Ausgabe', Daten, (Fehler) => { wenn (err) throw err; }); console.log('Neue Datei wurde erstellt!'); }); Dieser Code übernimmt einfach den Eingabedateinamen und -pfad und schreibt ihn nach dem Versuch, die Datei zu lesen, in den Zielpfad, was bei kleinen Dateien kein Problem darstellt. Nehmen wir nun an, wir haben eine große Datei (größer als 4 GB), die wir mit diesem Programm sichern müssen. Nehmen wir als Beispiel meinen 7,4G Ultra-High-Definition-4K-Film. Ich verwende den obigen Programmcode, um ihn aus dem aktuellen Verzeichnis in ein anderes Verzeichnis zu kopieren. $ node basic_copy.js cartoonMovie.mkv ~/Dokumente/bigMovie.mkv Dann bekam ich diese Fehlermeldung unter Ubuntu (Linux):
Wie Sie sehen, tritt der Fehler beim Lesen der Datei auf, da NodeJS nur das Schreiben von maximal 2 GB Daten in seinen Puffer zulässt. Um dieses Problem zu lösen, sollten Sie bei E/A-intensiven Vorgängen (Kopieren, Verarbeiten, Komprimieren usw.) die Speichersituation berücksichtigen. Streams und Puffer in NodeJSUm das obige Problem zu lösen, benötigen wir eine Möglichkeit, große Dateien in viele Dateiblöcke aufzuteilen, und wir benötigen eine Datenstruktur zum Speichern dieser Dateiblöcke. Ein Puffer ist eine Struktur zum Speichern binärer Daten. Als Nächstes benötigen wir eine Möglichkeit zum Lesen und Schreiben von Dateiblöcken, und Streams bietet diese Möglichkeit. PufferMit dem Buffer-Objekt können wir ganz einfach einen Puffer erstellen. let buffer = new Buffer(10); # 10 ist das Volumen des Puffers console.log(buffer); # druckt <Buffer 00 00 00 00 00 00 00 00 00 00> In neueren Versionen von NodeJS (>8) können Sie auch so schreiben. Lassen Sie den Puffer = neuer Puffer.alloc(10); console.log(Puffer); # druckt <Puffer 00 00 00 00 00 00 00 00 00 00> Wenn wir bereits über Daten wie ein Array oder einen anderen Datensatz verfügen, können wir einen Puffer dafür erstellen. let name = "Node JS DEV"; Lassen Sie den Puffer = Puffer.from(Name); console.log(Puffer) # druckt <Puffer 4e 6f 64 65 20 4a 53 20 44 45 5> Puffer verfügen über einige wichtige Methoden wie buffer.toString() und buffer.toJSON(), mit denen Sie einen Drilldown in die von ihnen gespeicherten Daten durchführen können. Um den Code zu optimieren, werden wir keine Rohpuffer direkt erstellen. NodeJS und die V8-Engine implementieren dies bereits, indem sie beim Verarbeiten von Streams und Netzwerk-Sockets interne Puffer (Warteschlangen) erstellen. StreamsEinfach ausgedrückt sind Streams wie beliebige Türen auf NodeJS-Objekten. In der Computervernetzung ist Ingress eine Eingabeaktion und Egress eine Ausgabeaktion. Wir werden diese Begriffe im Folgenden weiterhin verwenden. Es gibt vier Arten von Streams:
Der folgende Satz erklärt deutlich, warum wir Streams verwenden sollten. Ein wichtiges Ziel der Stream-API (und insbesondere der Methode stream.pipe()) besteht darin, die Datenpufferung auf ein akzeptables Maß zu begrenzen, sodass Quellen und Ziele mit unterschiedlichen Geschwindigkeiten den verfügbaren Speicher nicht verstopfen. Wir müssen die Aufgabe irgendwie erledigen, ohne das System zu überlasten. Dies haben wir am Anfang des Artikels erwähnt. Im obigen Diagramm haben wir zwei Arten von Streams: lesbare Streams und beschreibbare Streams. Die Methode .pipe() ist eine sehr einfache Methode, um einen lesbaren Stream mit einem beschreibbaren Stream zu verbinden. Wenn Sie das obige Diagramm nicht verstehen, machen Sie sich keine Sorgen. Nachdem Sie sich unsere Beispiele angesehen haben, können Sie zum Diagramm zurückkehren und alles wird einen Sinn ergeben. Pfeifen sind ein faszinierender Mechanismus, und wir werden sie anhand von zwei Beispielen veranschaulichen. Lösung 1 (Verwenden Sie einfach Streams zum Kopieren von Dateien)Lassen Sie uns eine Lösung für das oben erwähnte Problem des Kopierens großer Dateien entwerfen. Zuerst erstellen wir zwei Flows und führen dann die nächsten Schritte aus. 1. Auf Datenblöcke aus einem lesbaren Stream warten 2. Schreiben Sie den Datenblock in den beschreibbaren Stream 3. Verfolgen Sie den Fortschritt des Dateikopierens Wir haben diesen Code streams_copy_basic.js genannt /* Eine Dateikopie mit Streams und Ereignissen - Autor: Naren Arya */ const stream = erfordern('stream'); const fs = erfordern('fs'); let Dateiname = Prozess.argv[2]; let destPath = process.argv[3]; const readabale = fs.createReadStream(Dateiname); const beschreibbar = fs.createWriteStream(destPath || "Ausgabe"); fs.stat(Dateiname, (err, stats) => { diese.Dateigröße = stats.size; dieser.Zähler = 1; dies.fileArray = Dateiname.split('.'); versuchen { this.duplicate = Zielpfad + "/" + this.fileArray[0] + '_Copy.' + this.fileArray[1]; } Fang(e) { console.exception('Der Dateiname ist ungültig! Bitte geben Sie den richtigen ein.'); } process.stdout.write(`Datei: ${this.duplicate} wird erstellt:`); readabale.on('Daten', (Block)=> { let Prozentsatz kopiert = ((Chunklänge * dieser Zähler) / diese Dateigröße) * 100; process.stdout.clearLine(); // aktuellen Text löschen verarbeiten.stdout.cursorTo(0); verarbeiten.stdout.write(`${Math.round(percentageCopied)}%`); beschreibbar.schreiben(Block); dieser.Zähler += 1; }); readabale.on('Ende', (e) => { process.stdout.clearLine(); // aktuellen Text löschen verarbeiten.stdout.cursorTo(0); process.stdout.write("Vorgang erfolgreich abgeschlossen"); zurückkehren; }); readabale.on('Fehler', (e) => { console.log("Es ist ein Fehler aufgetreten: ", e); }); beschreibbar.on('fertig', () => { console.log("Dateikopie erfolgreich erstellt!"); }); }); In diesem Programm erhalten wir zwei vom Benutzer übergebene Dateipfade (Quelldatei und Zieldatei) und erstellen dann zwei Streams, um Datenblöcke vom lesbaren Stream in den beschreibbaren Stream zu übertragen. Anschließend definieren wir einige Variablen, um den Fortschritt des Dateikopiervorgangs zu verfolgen und ihn dann auf der Konsole (in diesem Fall „Konsole“) auszugeben. Gleichzeitig abonnieren wir auch einige Veranstaltungen: Daten: Wird ausgelöst, wenn ein Datenblock gelesen wird Ende: Wird ausgelöst, wenn ein Datenblock vom lesbaren Stream gelesen wird Fehler: Wird ausgelöst, wenn beim Lesen eines Datenblocks ein Fehler auftritt Durch Ausführen dieses Programms können wir die Aufgabe des Kopierens einer großen Datei (hier 7,4 GB) erfolgreich abschließen. $ Zeitknoten streams_copy_basic.js cartoonMovie.mkv ~/Documents/4kdemo.mkv Wenn wir jedoch den Speicherstatus des Programms während des Betriebs über den Taskmanager beobachten, besteht weiterhin ein Problem. 4,6 GB? Der Speicherverbrauch unseres Programms während der Ausführung ist hier unsinnig und kann mit hoher Wahrscheinlichkeit andere Anwendungen blockieren. was ist passiert? Wenn Sie sich die Lese- und Schreibraten in der obigen Abbildung genau ansehen, werden Sie einige Hinweise finden. Festplattenlesezugriff: 53,4 MiB/s Festplattenschreibzugriff: 14,8 MiB/s Dies bedeutet, dass die Hersteller immer schneller produzieren und die Verbraucher nicht mithalten können. Um den gelesenen Datenblock zu speichern, speichert der Computer die überschüssigen Daten im Arbeitsspeicher des Geräts. Aus diesem Grund kommt es zu einem Anstieg des RAM. Der obige Code läuft auf meinem Rechner in 3 Minuten und 16 Sekunden ...
Lösung 2 (Dateikopieren basierend auf Streams und automatischem Gegendruck)Um die oben genannten Probleme zu beheben, können wir das Programm so ändern, dass die Lese- und Schreibgeschwindigkeit der Festplatte automatisch angepasst wird. Dieser Mechanismus ist Gegendruck. Wir müssen nicht viel tun, sondern nur den lesbaren Stream in den beschreibbaren Stream importieren und NodeJS kümmert sich um den Gegendruck. Nennen wir dieses Programm streams_copy_efficient.js /* Eine Dateikopie mit Streams und Piping - Autor: Naren Arya */ const stream = erfordern('stream'); const fs = erfordern('fs'); let Dateiname = Prozess.argv[2]; let destPath = process.argv[3]; const readabale = fs.createReadStream(Dateiname); const beschreibbar = fs.createWriteStream(destPath || "Ausgabe"); fs.stat(Dateiname, (err, stats) => { diese.Dateigröße = stats.size; dieser.Zähler = 1; dies.fileArray = Dateiname.split('.'); versuchen { this.duplicate = Zielpfad + "/" + this.fileArray[0] + '_Copy.' + this.fileArray[1]; } Fang(e) { console.exception('Der Dateiname ist ungültig! Bitte geben Sie den richtigen ein.'); } process.stdout.write(`Datei: ${this.duplicate} wird erstellt:`); readabale.on('Daten', (Block) => { let Prozentsatz kopiert = ((Chunklänge * dieser Zähler) / diese Dateigröße) * 100; process.stdout.clearLine(); // aktuellen Text löschen verarbeiten.stdout.cursorTo(0); verarbeiten.stdout.write(`${Math.round(percentageCopied)}%`); dieser.Zähler += 1; }); readabale.pipe(writeable); // Autopilot EIN! // Falls es beim Kopieren zu einer Unterbrechung kommt beschreibbar.auf('unpipe', (e) => { process.stdout.write("Das Kopieren ist fehlgeschlagen!"); }); }); In diesem Beispiel haben wir den vorherigen Datenblock-Schreibvorgang durch eine Codezeile ersetzt. readabale.pipe(writeable); // Autopilot EIN! Die ganze Magie geschieht in diesem Rohr. Es steuert die Geschwindigkeit der Lese- und Schreibvorgänge auf der Festplatte, um den Hauptspeicher (RAM) nicht zu verstopfen. Führen Sie es aus. $ Zeitknoten streams_copy_efficient.js cartoonMovie.mkv ~/Documents/4kdemo.mkv Wir haben dieselbe große Datei (7,4 GB) kopiert und schauen uns die Speicherauslastung an. Schock! Jetzt belegt das Node-Programm nur noch 61,9 MiB Speicher. Wenn Sie die Lese- und Schreibraten beobachten: Festplattenlesezugriff: 35,5 MiB/s Festplattenschreibzugriff: 35,5 MiB/s Aufgrund des Gegendrucks bleiben die Lese- und Schreibraten jederzeit konstant. Noch überraschender ist, dass dieser optimierte Programmcode 13 Sekunden schneller ist als der vorherige.
Dank NodeJS-Streams und -Pipes wurde die Speicherlast um 98,68 % reduziert und auch die Ausführungszeit verkürzt. Aus diesem Grund ist die Pipeline eine starke Präsenz. 61,9 MiB ist die Größe des Puffers, der durch den lesbaren Stream erstellt wird. Wir können dem Pufferblock auch eine benutzerdefinierte Größe zuweisen, indem wir die Lesemethode für den lesbaren Stream verwenden. const readabale = fs.createReadStream(Dateiname); lesbar.lesen(Anzahl_Bytes_Größe); Zusätzlich zum lokalen Kopieren von Dateien kann diese Technik auch verwendet werden, um viele E/A-Vorgangsprobleme zu optimieren:
abschließendDie Motivation für das Schreiben dieses Artikels besteht hauptsächlich darin, zu veranschaulichen, dass wir versehentlich Code mit schlechter Leistung schreiben können, selbst wenn NodeJS eine gute API bereitstellt. Wenn wir den integrierten Tools mehr Aufmerksamkeit schenken würden, könnten wir den Programmablauf besser optimieren. Oben finden Sie detaillierte Informationen zur Verwendung von Node.js zum Schreiben speichereffizienter Anwendungen. Weitere Informationen zu Node.js finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM! Das könnte Sie auch interessieren:
|
<<: Ein genauerer Blick auf SQL-Injection
>>: So konfigurieren Sie ein SSL-Zertifikat in Nginx, um den HTTPS-Dienst zu implementieren
Inhaltsverzeichnis Vorwort 1. Technisches Prinzip...
Der folgende Fall überprüft die Wissenspunkte der...
Zweck Verstehen Sie die Rolle von nextTick und me...
In diesem Artikel wird der spezifische JavaScript...
Detaillierte Erklärung der Verwendung von DECIMAL...
Wie deinstalliere ich Mysql vollständig? Befolgen...
In diesem Tutorial erfahren Sie alles über die In...
In MySQL können Sie mehrere Indizes für eine Tabe...
Code zur Änderung des CSS-Bildlaufleistenstils .s...
In vielen Projekten muss eine Countdown-Funktion ...
1. MS SQL SERVER 2005 --1. Löschen Sie das Protok...
Der Zweck der Einrichtung eines MySQL-Abfragecach...
Inhaltsverzeichnis 1. Geben Sie ein Verzeichnis e...
Fügen Sie dem el-form-Formular Regeln hinzu: Defi...
Inhaltsverzeichnis 1 Verwendung von v-if und v-sh...