Terminez d'abord "Utiliser Socket.IO pour les rooms, namespaces et la reconnexion automatique" pour débloquer cette leçon.Aller à la leçon précédente

Le problème avec un serveur unique

Chaque connexion WebSocket vit dans la mémoire d'un seul processus Node.js. Si vous exécutez un seul serveur, tous les clients s'y connectent et vous pouvez diffuser un message avec une simple boucle en mémoire. Dès que vous lancez une deuxième instance derrière un load balancer, les clients sont répartis entre les processus. Un message qui arrive sur le serveur A ne peut jamais atteindre un client connecté au serveur B, car les deux processus ne partagent aucune mémoire.

La mise à l'échelle horizontale a donc besoin d'un moyen pour les serveurs de communiquer entre eux. Redis Pub/Sub est l'option la plus légère : chaque serveur s'abonne à un ensemble de canaux, et lorsqu'un serveur publie, Redis distribue le message à chaque abonné.

schéma montrant deux serveurs Node.js derrière un load balancer, chacun avec ses propres clients connectés, et Redis placé entre eux comme bus pub/sub

Mise en place d'un adaptateur Redis

L'adaptateur `socket.io-redis` se branche sur l'API d'adaptateur intégrée de Socket.IO et remplace la diffusion en mémoire par défaut par Redis Pub/Sub. Installez-le sur chaque instance :

bash
npm install socket.io redis socket.io-redis

Ensuite, dans le point d'entrée de votre serveur, créez un adaptateur lié à un client Redis et attachez-le au serveur Socket.IO :

javascript
const http = require('http');
const { Server } = require('socket.io');
const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');
const httpServer = http.createServer();
const io = new Server(httpServer);
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
io.adapter(createAdapter(pubClient, subClient));
io.listen(3000);
});

Une fois attaché, chaque appel à `io.emit`, `io.to(room).emit` ou `socket.broadcast.emit` passe par Redis. Les serveurs abonnés à la même instance Redis diffuseront l'événement à leurs propres sockets connectés, quel que soit le serveur qui l'a émis.

Diffusion entre instances

Avec l'adaptateur en place, votre code de diffusion ne change pas. Un handler de chat qui fonctionne sur un serveur fonctionne sur dix :

javascript
io.on('connection', (socket) => {
socket.on('join', (room) => {
socket.join(room);
});
socket.on('message', (room, text) => {
io.to(room).emit('message', {
user: socket.data.username,
text,
time: Date.now()
});
});
});

Lorsque le client A sur le serveur 1 émet `message`, Socket.IO sérialise l'événement, le publie sur Redis via un canal scopé par room, et chaque autre serveur le récupère puis le diffuse à ses sockets locaux présents dans cette room. Du point de vue du client, le chat est temps réel et global.

diagramme montrant un message émis sur le serveur 1, passant par Redis Pub/Sub, puis diffusé aux serveurs 2 et 3 qui le transmettent chacun à leurs clients locaux

Faire évoluer l'adaptateur au-delà de deux nœuds

Le pattern ci-dessus évolue de façon linéaire. Quelques règles opérationnelles le maintiennent en bonne santé à des nombres de nœuds plus élevés.

Premièrement, exécutez Redis avec une persistance raisonnable. Les messages Pub/Sub ne sont pas stockés, mais l'adaptateur utilise aussi des clés Redis pour l'état de handshake. Un flush au crash forcera les clients à se reconnecter, ce qui est acceptable pour du chat mais pas pour des charges stateful.

Deuxièmement, configurez les sticky sessions sur votre load balancer uniquement pour le WebSocket upgrade, ou acceptez le coût des reconnexions. Sans stickiness, un client qui se reconnecte peut atterrir sur un serveur différent et devoir rejoindre les rooms. Socket.IO gère cela de façon transparente car l'appartenance aux rooms est locale au serveur, mais le client manque brièvement des événements pendant le handshake.

Troisièmement, surveillez le nombre de canaux Pub/Sub et le nombre d'abonnés Redis. Chaque namespace Socket.IO ajoute des canaux, donc un déploiement avec plusieurs namespaces multiplie l'empreinte en canaux.

Enfin, si Redis devient le goulot d'étranglement, vous pouvez sharder le Pub/Sub sur un Redis Cluster. Le package `@socket.io/redis-adapter` supporte le mode cluster nativement en acceptant plusieurs options de connexion.

Pièges courants

Une erreur fréquente consiste à créer l'adaptateur avant que les deux clients Redis ne soient connectés. Publier sur un client non connecté lève une erreur ou perd silencieusement le message. Faites toujours un `await` sur les appels connect, ou enveloppez-les dans un callback ready comme indiqué plus haut.

Une autre erreur consiste à utiliser un seul client Redis à la fois pour la publication et l'abonnement. Redis exige une connexion d'abonné dédiée car un client en mode subscribe ne peut pas exécuter de commandes normales. L'appel `pubClient.duplicate()` dans l'exemple crée cette seconde connexion pour vous.

Un problème plus subtil consiste à s'appuyer sur `io.local` pour de la logique cross-server. `io.local.emit` ne se déclenche que sur le serveur courant et contourne Redis, ce qui est utile pour du diagnostic par instance, mais casse dès que vous supposez que le message a atteint tous les nœuds. Utilisez `io.emit` simple pour tout ce qui doit atteindre tous les clients.

Résumé

La mise à l'échelle horizontale des serveurs WebSocket repose sur deux éléments : un load balancer devant N instances Node.js, et Redis Pub/Sub comme bus de messages entre elles. L'adaptateur `socket.io-redis` encapsule le bus pour que le code applicatif reste inchangé. Les rooms, broadcasts et namespaces fonctionnent tous entre instances de la même façon que sur une seule instance. Les principales préoccupations opérationnelles sont la disponibilité de Redis, les sticky sessions pour les WebSocket upgrades, et le fait de ne jamais confondre `io.local.emit` avec `io.emit`.

Entraînement

Point de contrôle de la leçon

Révisez la leçon et recevez des conseils sur votre travail.

0/1
Répondu · 0/1