Skalierung von WebSocket-Diensten mit Redis Pub/Sub in Node.js
Ethan Miller
Product Engineer · Leapcell

Einführung
In der modernen Webentwicklung sind Echtzeitfunktionalitäten, die über WebSockets bereitgestellt werden, für Anwendungen von Chat-Plattformen bis hin zu kollaborativen Bearbeitungswerkzeugen unverzichtbar geworden. Wenn Anwendungen an Popularität und Komplexität gewinnen, stößt der typische WebSocket-Server mit einer einzigen Instanz schnell an seine Grenzen in Bezug auf Skalierbarkeit und Fehlertoleranz. Wenn ein einzelner Server ausfällt, werden alle verbundenen Clients getrennt, und das Lastenausgleich wird zu einer erheblichen Herausforderung. Die Bewältigung dieser Bedenken beinhaltet oft die Bereitstellung mehrerer Instanzen des WebSocket-Dienstes. Es entsteht jedoch ein entscheidendes Problem: Wie kommunizieren diese isolierten Instanzen miteinander, um Nachrichten an alle relevanten Clients zu übertragen, unabhängig davon, an welche Instanz sie verbunden sind? Hier glänzt Redis Pub/Sub als elegante und effiziente Lösung für die Interprozesskommunikation, die es uns ermöglicht, robuste und skalierbare Multi-Instanz-WebSocket-Architekturen zu erstellen.
Kernkonzepte verstehen
Bevor wir uns mit den Implementierungsdetails befassen, wollen wir einige grundlegende Konzepte klären, die für unsere Diskussion zentral sind:
- WebSocket: Ein Kommunikationsprotokoll, das über eine einzige TCP-Verbindung Full-Duplex-Kommunikationskanäle bereitstellt. Es ermöglicht den bidirektionalen Echtzeitaustausch von Daten zwischen einem Client und einem Server.
- Node.js: Eine JavaScript-Laufzeitumgebung, die auf der V8-JavaScript-Engine von Chrome basiert. Sein ereignisgesteuertes, nicht blockierendes I/O-Modell macht es für Echtzeitanwendungen wie WebSocket-Server äußerst effizient.
- Redis: Ein Open-Source-Datenspeicher, der als Datenbank, Cache und Message Broker verwendet wird. Seine blitzschnelle Leistung und die Unterstützung verschiedener Datenstrukturen, einschließlich Pub/Sub, machen ihn ideal für Messaging mit hohem Durchsatz.
- Pub/Sub (Publish/Subscribe): Ein Nachrichtenmuster, bei dem Sender (Publisher) Nachrichten nicht direkt an bestimmte Empfänger (Subscriber) senden, sondern veröffentlichte Nachrichten in Klassen kategorisieren, ohne zu wissen, welche Subscriber möglicherweise vorhanden sind. Subscriber äußern Interesse an einer oder mehreren Nachrichtenklassen und erhalten nur Nachrichten, die sie interessieren.
- Multi-Instanz-Bereitstellung: Ausführen mehrerer Kopien derselben Anwendung hinter einem Load Balancer. Dies verbessert die Skalierbarkeit, indem Client-Anfragen auf Instanzen verteilt werden, und erhöht die Fehlertoleranz, indem sichergestellt wird, dass der Dienst verfügbar bleibt, auch wenn eine Instanz ausfällt.
Die Herausforderung von Multi-Instanz-WebSockets
Betrachten Sie ein Szenario, in dem Benutzer mit einer Chat-Anwendung verbunden sind. Wenn wir zwei Node.js WebSocket-Serverinstanzen, Instanz A
und Instanz B
, haben, sendet ein Benutzer, der mit Instanz A
verbunden ist, eine Nachricht. Woher weiß Instanz B
von dieser Nachricht, um sie an die mit ihr verbundenen Benutzer zu senden? Ohne einen gemeinsamen Kommunikationsmechanismus kann Instanz A
die Nachricht nur an ihre eigenen verbundenen Clients senden. Hier überbrückt Redis Pub/Sub die Lücke.
Skalierung von WebSockets mit Redis Pub/Sub
Das Kernprinzip besteht darin, Redis als zentralen Nachrichtenbus zu verwenden. Wenn eine WebSocket-Serverinstanz eine Nachricht von einem Client empfängt, veröffentlicht sie diese Nachricht nicht nur an ihre lokalen Clients, sondern auch an einen bestimmten Redis-Kanal. Alle anderen WebSocket-Serverinstanzen, die denselben Redis-Kanal abonniert haben, empfangen diese Nachricht und senden sie dann an ihre jeweiligen verbundenen Clients. Dies stellt sicher, dass jede Nachricht alle relevanten Clients erreicht, unabhängig davon, mit welcher Instanz sie verbunden sind.
Implementierungsdetails
Lassen Sie uns dies mit einem praktischen Node.js-Beispiel unter Verwendung der ws
-Bibliothek für WebSockets und ioredis
für den Redis-Client veranschaulichen.
Installieren Sie zuerst die erforderlichen Pakete:
npm install ws ioredis
Nun erstellen wir eine vereinfachte server.js
-Datei für unseren WebSocket-Dienst:
// server.js const WebSocket = require('ws'); const Redis = require('ioredis); // Stellen Sie sicher, dass ein Redis-Server lokal läuft oder geben Sie seine Verbindungszeichenfolge an const redisPublisher = new Redis(); // Standardmäßig localhost:6379 const redisSubscriber = new Redis(); // Separater Client für die Abonnementfunktion const wss = new WebSocket.Server({ port: 8080 }); console.log('WebSocket-Server gestartet unter Port 8080'); // Array zum Speichern aller verbundenen WebSocket-Clients für diese spezifische Instanz const clients = new Set(); wss.on('connection', ws => { console.log('Client verbunden'); clients.add(ws); // Wenn eine Nachricht von einem Client auf dieser Instanz empfangen wird ws.on('message', message => { console.log(`Nachricht vom Client empfangen: ${message}`); // Veröffentlichen Sie die Nachricht in einem Redis-Kanal redisPublisher.publish('chat_messages', message.toString()); }); ws.on('close', () => { console.log('Client getrennt'); clients.delete(ws); }); ws.on('error', error => { console.error('WebSocket-Fehler:', error); }); }); // Abonnement des Redis-Kanals für Nachrichten von anderen Instanzen redisSubscriber.subscribe('chat_messages', (err, count) => { if (err) { console.error('Fehler beim Abonnieren von Redis-Kanal:', err); } else { console.log(`Abonniert ${count} Redis-Kanal(e).`); } }); // Wenn eine Nachricht von Redis empfangen wird (veröffentlicht von einer beliebigen Instanz) redisSubscriber.on('message', (channel, message) => { console.log(`Nachricht vom Redis-Kanal "${channel}" empfangen: ${message}`); // Senden Sie diese Nachricht an alle Clients, die mit dieser speziellen Instanz verbunden sind clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(message.toString()); } }); }); // Graceful Shutdown verarbeiten process.on('SIGINT', () => { console.log('WebSocket-Server wird heruntergefahren...'); wss.close(() => { console.log('WebSocket-Server geschlossen.'); redisPublisher.quit(); redisSubscriber.quit(); process.exit(0); }); });
Um dies zu testen, können Sie mehrere Instanzen dieses Servers auf verschiedenen Ports ausführen (z. B. node server.js
für Port 8080 und modifizieren Sie den Port auf 8081 für eine weitere Instanz). Normalerweise würden Sie einen Load Balancer vor diesen Instanzen verwenden, aber für einen grundlegenden Test reichen direkte Verbindungen aus.
Beispiel-Client (HTML/JavaScript)
<!-- index.html --> <!DOCTYPE html> <html> <head> <title>WebSocket-Chat</title> </head> <body> <h1>WebSocket-Chat</h1> <input type="text" id="messageInput" placeholder="Geben Sie Ihre Nachricht ein..."> <button onclick="sendMessage()">Senden</button> <ul id="messages"></ul> <script> // Stellen Sie eine Verbindung zu einer Ihrer WebSocket-Instanzen her, z.B. auf Port 8080 oder 8081 const ws = new WebSocket('ws://localhost:8080'); // Oder ws://localhost:8081 für eine andere Instanz ws.onopen = () => { console.log('Mit WebSocket-Server verbunden'); }; ws.onmessage = event => { const messagesList = document.getElementById('messages'); const listItem = document.createElement('li'); listItem.textContent = event.data; messagesList.appendChild(listItem); }; ws.onclose = () => { console.log('Vom WebSocket-Server getrennt'); }; ws.onerror = error => { console.error('WebSocket-Fehler:', error); }; function sendMessage() { const input = document.getElementById('messageInput'); const message = input.value; if (message) { ws.send(message); input.value = ''; } } </script> </body> </html>
Anwendungsszenarien
Diese Architektur ist vorteilhaft für:
- Echtzeit-Chat-Anwendungen: Sicherstellung der Zustellung von Nachrichten an alle Teilnehmer über verschiedene Serverinstanzen hinweg.
- Live-Dashboards: Aktualisierung von Datenpunkten für alle verbundenen Betrachter, unabhängig von ihrer Serververbindung.
- Kollaboratives Bearbeiten: Synchronisierung von Änderungen zwischen mehreren Benutzern, die am selben Dokument arbeiten.
- Gaming: Übertragung von Spielstatusaktualisierungen an alle Spieler.
Fazit
Durch die Integration von Redis Pub/Sub mit Node.js WebSocket-Diensten überwinden wir effektiv die Einschränkungen von Single-Instanz-Bereitstellungen. Dieses robuste Muster ermöglicht horizontale Skalierung, verbessert die Fehlertoleranz und gewährleistet eine nahtlose Echtzeitkommunikation in einem verteilten System. Die Nutzung von Redis als zentraler Nachrichtenbroker ermöglicht es jeder WebSocket-Instanz, unabhängig zu agieren und dennoch global zu kommunizieren, wodurch Ihre Echtzeitanwendungen hochgradig skalierbar und resilient werden. Es ist eine leistungsstarke Kombination für den Aufbau moderner, hochperformanter Webdienste.