Einleitung: Warum braucht man Aufgabenwarteschlangen in JavaScript?
\nIn der modernen Webentwicklung mit Node.js treten oft Aufgaben auf, die nicht sofort im Rahmen einer einzelnen HTTP-Anfrage ausgeführt werden können. Das Versenden von E-Mail-Newslettern, die Generierung von PDF-Berichten, die Bildverarbeitung, die Synchronisation mit externen APIs – all diese Vorgänge benötigen Zeit und Ressourcen. Wenn man sie synchron ausführt, wartet der Benutzer zu lange auf eine Antwort und der Server kann überlastet werden.
\nAufgabenwarteschlangen (Job Queues) lösen dieses Problem. Sie ermöglichen es, schwere Operationen zu verschieben, die Last zu verteilen, eine erneute Verarbeitung bei Fehlern zu garantieren und das System horizontal zu skalieren. In diesem Artikel analysieren wir die praktischen Aspekte der Arbeit mit Warteschlangen in JavaScript (Node.js): von der Theorie bis zum Schreiben von funktionsfähigem Code mit beliebten Bibliotheken.
\n\nGrundlegende Konzepte von Aufgabenwarteschlangen
\nBevor wir zum Code übergehen, ist es wichtig, die Schlüsselelemente jeder Aufgabenwarteschlange zu verstehen:
\n- \n
- Job (Aufgabe) – eine Arbeitseinheit, die ausgeführt werden muss. Enthält normalerweise Daten (Payload) und Metainformationen (Priorität, Versuche, Status). \n
- Queue (Warteschlange) – eine Datenstruktur (oft auf Basis von Redis), die Aufgaben in der Reihenfolge ihres Eingangs speichert. \n
- Worker (Arbeiter) – ein Prozess oder Thread, der Aufgaben aus der Warteschlange abholt und ausführt. \n
- Producer (Erzeuger) – der Teil des Systems, der Aufgaben zur Warteschlange hinzufügt. \n
- Consumer (Verbraucher) – ein Synonym für Worker, der Aufgaben verarbeitet. \n
In Node.js sind die beliebtesten Bibliotheken für die Arbeit mit Warteschlangen Bull und Kue. Beide verwenden Redis als Nachrichtenbroker. Bull ist moderner und leistungsfähiger, Kue ist einfacher für den Einstieg. In diesem Artikel konzentrieren wir uns auf Bull als empfohlene Lösung für die Produktion.
\n\nPraktische Implementierung einer Warteschlange mit Bull
\nInstallation und Einrichtung
\nInstallieren Sie zunächst Redis (lokal oder über Docker) und fügen Sie die Bibliothek zum Projekt hinzu:
\nnpm install bull\nDie grundlegende Initialisierung einer Warteschlange sieht so aus:
\nconst Queue = require('bull');\nconst imageQueue = new Queue('image processing', {\n redis: { host: 'localhost', port: 6379 }\n});\n\nHinzufügen von Aufgaben (Producer)
\nDer Producer fügt eine Aufgabe mit Daten und Optionen hinzu:
\napp.post('/upload', async (req, res) => {\n const job = await imageQueue.add({\n imageUrl: req.body.url,\n userId: req.user.id\n }, {\n attempts: 3, // Anzahl der Versuche bei Fehlern\n backoff: 5000, // Verzögerung zwischen Versuchen (ms)\n removeOnComplete: true // Aufgabe nach Erfolg löschen\n });\n res.json({ jobId: job.id });\n});\n\nVerarbeitung von Aufgaben (Worker)
\nDer Worker ist ein separater Prozess (oder derselbe Prozess, aber asynchron), der die Warteschlange abhört:
\nimageQueue.process(async (job, done) => {\n try {\n const { imageUrl, userId } = job.data;\n console.log(`Verarbeitung des Bildes ${imageUrl} für Benutzer ${userId}`);\n // Hier Ihre Logik: herunterladen, Größe ändern, speichern\n await processImage(imageUrl);\n done(); // Signal für erfolgreichen Abschluss\n } catch (error) {\n done(new Error('Verarbeitungsfehler: ' + error.message));\n }\n});\n\nFehlerbehandlung und Wiederholungsversuche
\nBull wiederholt die Aufgabe automatisch, wenn der Worker mit einem Fehler beendet wurde. Sie können die Strategie für Wiederholungsversuche konfigurieren:
\nconst job = await imageQueue.add(data, {\n attempts: 5,\n backoff: { type: 'exponential', delay: 2000 }\n});\nSie können auch auf Ereignisse der Warteschlange zur Überwachung hören:
\nimageQueue.on('completed', (job, result) => {\n console.log(`Aufgabe ${job.id} abgeschlossen`);\n});\nimageQueue.on('failed', (job, err) => {\n console.error(`Aufgabe ${job.id} fehlgeschlagen: ${err.mes