Das Modul async_hooks ist eine experimentelle API, die in Version v8.0.0 offiziell zu Node.js hinzugefügt wurde. Wir haben es auch in der Produktionsumgebung unter Version v8.xx implementiert. Was sind also async_hooks? async_hooks bietet eine API zum Verfolgen asynchroner Ressourcen, bei denen es sich um Objekte mit zugehörigen Rückrufen handelt. Kurz gesagt kann das Modul async_hooks zum Verfolgen asynchroner Rückrufe verwendet werden. Wie nutzen Sie also diese Tracking-Funktion und welche Probleme können bei ihrer Nutzung auftreten? Grundlegendes zu async_hooksasync_hooks unter der Version v8.xx besteht hauptsächlich aus zwei Teilen, einer ist createHook zum Verfolgen des Lebenszyklus und der andere ist AsyncResource zum Erstellen asynchroner Ressourcen. const { createHook, AsyncResource, executionAsyncId } = erforderlich('async_hooks') const hook = erstelleHook({ init (asyncId, Typ, triggerAsyncId, Ressource) {}, vor (asyncId) {}, nach (asyncId) {}, zerstören (asyncId) {} }) hook.aktivieren() Funktion fn () { console.log(ausführungAsyncId()) } const asyncResource = neue AsyncResource('demo'). asyncResource.run(fn) asyncResource.run(fn) asyncResource.emitDestroy() Die Bedeutung und das Ausführungsergebnis des obigen Codes sind:
Hinter den asynchronen Operationen wie Async, Await, Promise-Syntax oder Requests, die wir häufig verwenden, stehen asynchrone Ressourcen, die auch diese Lifecycle-Hook-Funktionen auslösen. Anschließend können wir in der Init-Hook-Funktion eine Zeigerbeziehung vom Kontext der asynchronen Ressourcenerstellung triggerAsyncId (übergeordnet) zur aktuellen asynchronen Ressource asyncId (untergeordnet) erstellen, die asynchronen Aufrufe nacheinander verbinden, einen vollständigen Aufrufbaum erhalten und die asyncId der asynchronen Ressource abrufen, die den aktuellen Rückruf über executionAsyncId() in der Rückruffunktion ausführt (d. h. fn im obigen Code) und die Quelle des Aufrufs aus der Aufrufkette zurückverfolgen. Gleichzeitig müssen wir auch beachten, dass init ein Hook für die asynchrone Ressourcenerstellung ist, kein Hook für die asynchrone Erstellung von Rückruffunktionen. Es wird nur einmal ausgeführt, wenn asynchrone Ressourcen erstellt werden. Welche Probleme bringt dies bei der tatsächlichen Verwendung mit sich? AnfrageverfolgungZum Zwecke der Ausnahmebehebung und Datenanalyse hoffen wir, die Anforderungs-ID im Anforderungsheader der vom Client gesendeten Anforderung automatisch zum Anforderungsheader jeder Anforderung hinzufügen zu können, die an die Mid- und Back-End-Dienste in unserem Node.js-Dienst mit Ada-Architektur gesendet wird. Der einfache Entwurf der Funktionsimplementierung sieht wie folgt aus:
Der Beispielcode lautet wie folgt: const http = erfordern('http'). const { createHook, executionAsyncId } = erforderlich('async_hooks') const fs = erfordern('fs') // Aufrufkette verfolgen und ein Speicherobjekt für die Aufrufkette erstellen const cache = {} const hook = erstelleHook({ init (asyncId, Typ, triggerAsyncId, Ressource) { wenn (Typ === 'TickObject') zurückgeben // Da console.log auch ein asynchrones Verhalten in Node.js ist, wird es den Init-Hook auslösen, sodass wir Protokolle nur über synchrone Methoden aufzeichnen können fs.appendFileSync('log.out', `init ${type}(${asyncId}: trigger: ${triggerAsyncId})\n`); // Bestimmen Sie, ob das Aufrufketten-Speicherobjekt initialisiert wurde, wenn (!cache[triggerAsyncId]) { cache[triggerAsyncId] = {} } // Teilen Sie den Speicher des übergeordneten Knotens mit der aktuellen asynchronen Ressource per Referenz cache[asyncId] = cache[triggerAsyncId] } }) hook.aktivieren() // http neu schreiben const httpRequest = http.request http.request = (Optionen, Rückruf) => { const client = httpRequest(Optionen, Rückruf) // Die in der asynchronen Ressource gespeicherte Request-ID zur aktuellen Anfrage abrufen und in den Header schreiben const requestId = cache[executionAsyncId()].requestId Konsole.log('Cache', Cache[Ausführungs-AsyncId()]) client.setHeader('Anforderungs-ID', Anforderungs-ID) Stammkunde } Funktion Timeout () { returniere neues Promise((lösen, ablehnen) => { setTimeout(auflösen, Math.random() * 1000) }) } // Dienst erstellen http .createServer(async (req, res) => { // Holen Sie die Anforderungs-ID der aktuellen Anforderung und schreiben Sie sie in den Speichercache [executionAsyncId()].requestId = req.headers['request-id'] //Simulieren Sie einige andere zeitaufwändige Operationen await timeout() // Anfrage senden http.request('http://www.baidu.com', (res) => {}) res.write('hallo\n') res.ende() }) .listen(3000) Führen Sie den Code aus und führen Sie einen Sendetest durch. Sie werden feststellen, dass die Anforderungs-ID korrekt abgerufen werden kann. fangenGleichzeitig müssen wir auch beachten, dass init ein Hook für die asynchrone Ressourcenerstellung ist, kein Hook für die Erstellung asynchroner Rückruffunktionen, und dass er nur einmal ausgeführt wird, wenn asynchrone Ressourcen erstellt werden. Der obige Code ist jedoch problematisch. Wie im Code des zuvor vorgestellten Moduls async_hooks gezeigt, kann eine asynchrone Ressource kontinuierlich verschiedene Funktionen ausführen, d. h. asynchrone Ressourcen können wiederverwendet werden. Insbesondere bei asynchronen Ressourcen wie TCP, die vom C/C++-Teil erstellt werden, können mehrere Anfragen dieselbe asynchrone TCP-Ressource verwenden. Infolgedessen wird in diesem Fall die anfängliche Init-Hook-Funktion nur einmal ausgeführt, wenn mehrere Anfragen beim Server eintreffen, was dazu führt, dass die Aufrufkettenverfolgung mehrerer Anfragen dieselbe triggerAsyncId verfolgt und somit auf denselben Speicher verweist. Um eine Überprüfung durchzuführen, ändern wir den vorherigen Code wie folgt. Der Speicherinitialisierungsteil speichert triggerAsyncId, um die Beobachtung der Tracking-Beziehung asynchroner Aufrufe zu erleichtern: wenn (!cache[triggerAsyncId]) { cache[triggerAsyncId] = { ID: triggerAsyncId } } Die Timeout-Funktion wird geändert, um zuerst einen Langzeitvorgang und dann einen Kurzzeitvorgang auszuführen: Funktion Timeout () { returniere neues Promise((lösen, ablehnen) => { setTimeout(auflösen, [1000, 5000].pop()) }) } Nach dem Neustart des Dienstes verwenden Sie Postman (nicht Curl, da Curl die Verbindung nach jeder Anfrage schließt, was eine Reproduktion unmöglich macht), um zwei aufeinanderfolgende Anfragen zu senden. Sie können die folgende Ausgabe beobachten:
Es zeigt sich, dass bei mehreren gleichzeitigen Vorgängen mit anderen Vorgängen, die zwischen dem Schreiben und Lesen der Speicherung unterschiedlich viel Zeit in Anspruch nehmen, der in der Anfrage gespeicherte Wert, der zuerst beim Server eintrifft, durch die Anfrage überschrieben wird, die später beim Server eintrifft, wodurch die vorherige Anfrage den falschen Wert liest. Natürlich können Sie sicherstellen, dass zwischen dem Schreiben und Lesen keine anderen zeitaufwändigen Vorgänge eingefügt werden, aber bei komplexen Diensten ist diese Art der mentalen Wartungsmethode offensichtlich unzuverlässig. An diesem Punkt müssen wir JS dazu bringen, vor jedem Lesen und Schreiben in einen neuen asynchronen Ressourcenkontext zu wechseln, d. h. eine neue asynchrone ID abzurufen, um diese Wiederverwendung zu vermeiden. Die folgenden Änderungen müssen am Aufrufkettenspeicherteil vorgenommen werden: const http = erfordern('http'). const { createHook, executionAsyncId } = erforderlich('async_hooks') const fs = erfordern('fs') const cache = {} const httpRequest = http.request http.request = (Optionen, Rückruf) => { const client = httpRequest(Optionen, Rückruf) const requestId = cache[executionAsyncId()].requestId Konsole.log('Cache', Cache[Ausführungs-AsyncId()]) client.setHeader('Anforderungs-ID', Anforderungs-ID) Stammkunde } // Extrahiere die Speicherinitialisierung in eine unabhängige Methode async function cacheInit (callback) { // Verwenden Sie die await-Operation, damit der Code nach await in einen neuen asynchronen Kontext wechselt. await Promise.resolve() cache[executionAsyncId()] = {} //Verwende Callback-Ausführung, damit nachfolgende Vorgänge zu diesem neuen asynchronen Kontext gehören return callback() } const hook = erstelleHook({ init (asyncId, Typ, triggerAsyncId, Ressource) { wenn (!cache[triggerAsyncId]) { // Init-Hook wird nicht mehr initialisiert return fs.appendFileSync('log.out', `Nicht mit der Methode cacheInit initialisiert`) } cache[asyncId] = cache[triggerAsyncId] } }) hook.aktivieren() Funktion Timeout () { returniere neues Promise((lösen, ablehnen) => { setTimeout(auflösen, [1000, 5000].pop()) }) } http .createServer(async (req, res) => { // Nachfolgende Operationen als Callbacks an cacheInit übergeben warte auf CacheInit (asynchrone Funktion fn() { cache[executionAsyncId()].requestId = req.headers['Anforderungs-ID'] warte auf Timeout() http.request('http://www.baidu.com', (res) => {}) res.write('hallo\n') res.ende() }) }) .listen(3000) Es ist erwähnenswert, dass diese Organisationsmethode mit Rückrufen sehr gut mit dem Middleware-Modell von Koajs übereinstimmt. asynchrone Funktions-Middleware (ctx, next) { warte auf Promise.resolve() cache[executionAsyncId()] = {} returniere nächstes() } NodeJs v14Diese Art der Erstellung eines neuen asynchronen Kontexts mit await Promise.resolve() erscheint immer ein wenig „ketzerisch“. Glücklicherweise bietet NodeJs v9.xx eine offizielle Implementierung zum Erstellen eines asynchronen Kontexts: asyncResource.runInAsyncScope. Noch besser: NodeJs v14.xx bietet direkt die offizielle Implementierung der asynchronen Aufrufkettendatenspeicherung, die Ihnen direkt dabei hilft, die drei Aufgaben der asynchronen Aufrufbeziehungsverfolgung, der Erstellung neuer asynchroner Startdokumente und der Datenverwaltung zu erledigen! Die API wird nicht im Detail vorgestellt. Wir werden die neue API direkt verwenden, um die vorherige Implementierung zu transformieren const { AsyncLocalStorage } = erfordern('async_hooks') // Erstellen Sie direkt eine asyncLocalStorage-Speicherinstanz, Sie müssen keine asynchronen Lebenszyklus-Hooks mehr verwalten const asyncLocalStorage = new AsyncLocalStorage() const Speicher = { aktivieren (Rückruf) { // Verwende die Run-Methode, um einen neuen Speicher zu erstellen. Nachfolgende Vorgänge müssen als Rückrufe der Run-Methode ausgeführt werden, um einen neuen asynchronen Ressourcenkontext zu verwenden: asyncLocalStorage.run({}, callback) }, get (Schlüssel) { returniere asyncLocalStorage.getStore()[Schlüssel] }, set (Schlüssel, Wert) { asyncLocalStorage.getStore()[Schlüssel] = Wert } } // http neu schreiben const httpRequest = http.request http.request = (Optionen, Rückruf) => { const client = httpRequest(Optionen, Rückruf) // Die Request-ID des asynchronen Ressourcenspeichers abrufen und in den Header schreiben client.setHeader('Anforderungs-ID', storage.get('Anforderungs-ID')) Stammkunde } // Mit http .createServer((req, res) => { storage.enable(asynchrone Funktion () { // Holen Sie die Anforderungs-ID der aktuellen Anforderung und schreiben Sie sie in den Speicher storage.set('requestId', req.headers['request-id']) http.request('http://www.baidu.com', (res) => {}) res.write('hallo\n') res.ende() }) }) .listen(3000) Wie Sie sehen, stimmt die Struktur der offiziellen Implementierung der API asyncLocalStorage.run auch mit der Implementierung unserer zweiten Version überein. Daher kann in Node.js v14.xx die Anforderungsverfolgungsfunktion einfach mithilfe des Moduls async_hooks implementiert werden. Dies ist das Ende dieses Artikels über die Verwendung des Moduls async_hooks für die Knotenanforderungsverfolgung. Weitere Informationen zur Knotenanforderungsverfolgung async_hooks finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, Sie werden 123WORDPRESS.COM auch in Zukunft unterstützen! Das könnte Sie auch interessieren:
|
<<: Detaillierte Erläuterung des Nginx-Strombegrenzungsmoduls in der Nginx-Quellcode-Recherche
>>: Eine kurze Erläuterung temporärer MySQL-Tabellen und abgeleiteter Tabellen
In einem aktuellen Unternehmen besteht die Anford...
Vorwort Wenn die Abfrageinformationen aus mehrere...
Übersicht über partitionierte MySQL-Tabellen Wir ...
Zunächst müssen Sie bestimmen, welche Felder oder...
Inhaltsverzeichnis Fehlender Stammspeicherort Off...
Drei Möglichkeiten, CSS einzuführen 1. Inline-Sti...
<br />Es ist nicht länger als zwei Jahre her...
Wenn Browser-Anbieter die Standards umgehen und ei...
Vorwort Ich bin kürzlich bei der Arbeit auf ein P...
Spring-Integration mit SpringMVC Die web.xml-Konf...
Ein Kollege hat mir einmal gesagt, ich solle eine...
Die Installationsmethode von MySQL5.7 rpm unter L...
Ich erwarte nicht, ein erfahrener Datenbankadmini...
mysql kopiert die Dateien im Datenverzeichnis, um...
Einführung: Alle Browser verfügen über Standardst...