El problema con un solo servidor
Cada conexión WebSocket vive en la memoria de un proceso de Node.js. Si ejecutas un solo servidor, todos los clientes se conectan a él y puedes difundir un mensaje con un simple bucle en memoria. En el momento en que ejecutas una segunda instancia detrás de un balanceador de carga, los clientes se reparten entre procesos. Un mensaje que llega al servidor A nunca puede alcanzar a un cliente conectado al servidor B, porque los dos procesos no comparten memoria.
El escalado horizontal necesita, por tanto, una forma de que los servidores se comuniquen entre sí. Redis Pub/Sub es la opción más ligera: cada servidor se suscribe a un conjunto de canales, y cuando un servidor publica, Redis reparte el mensaje a cada suscriptor.
Conectando un adaptador de Redis
El adaptador `socket.io-redis` se conecta a la API de adaptador integrada de Socket.IO y reemplaza la difusión en memoria por defecto con Redis Pub/Sub. Instálalo en cada instancia:
Luego, en el punto de entrada de tu servidor, crea un adaptador vinculado a un cliente de Redis y conéctalo al servidor de Socket.IO:
Una vez conectado, cada llamada a `io.emit`, `io.to(room).emit`, o `socket.broadcast.emit` pasa por Redis. Los servidores suscritos a la misma instancia de Redis entregarán el evento a sus propios sockets conectados, sin importar qué servidor lo originó.
Difusión entre instancias
Con el adaptador en su lugar, tu código de difusión no cambia. Un handler de sala de chat que funciona en un servidor funciona en diez:
Cuando el cliente A en el servidor 1 emite `message`, Socket.IO serializa el evento, lo publica en Redis en un canal scoped a la sala, y cada otro servidor lo recoge y lo reparte a sus sockets locales en esa sala. Desde la perspectiva del cliente, el chat es en tiempo real y global.
Escalando el adaptador más allá de dos nodos
El patrón anterior escala de forma lineal. Algunas reglas operativas lo mantienen saludable con un mayor número de nodos.
Primero, ejecuta Redis con una persistencia razonable. Los mensajes de Pub/Sub no se almacenan, pero el adaptador también usa claves de Redis para el estado del handshake. Un flush al崩溃 forçará a los clientes a reconectarse, lo cual es aceptable para chat pero no para cargas de trabajo con estado.
Segundo, configura sticky sessions en tu balanceador de carga solo para el upgrade de WebSocket, o acepta el coste de las reconexiones. Sin stickiness, un cliente que se reconecta puede caer en un servidor diferente y tener que volver a unirse a las salas. Socket.IO maneja esto de forma transparente porque la pertenencia a las salas es local al servidor, pero el cliente pierde eventos brevemente durante el handshake.
Tercero, monitoriza el número de canales y el número de suscriptores de Redis Pub/Sub. Cada namespace de Socket.IO añade canales, por lo que un despliegue único con varios namespaces multiplica la huella de canales.
Finalmente, si Redis se convierte en el cuello de botella, puedes fragmentar Pub/Sub en un Redis Cluster. El paquete `@socket.io/redis-adapter` soporta el modo cluster de forma nativa aceptando múltiples opciones de conexión.
Errores comunes
Un error frecuente es crear el adaptador antes de que ambos clientes de Redis se hayan conectado. Publicar en un cliente no conectado lanza una excepción o descarta silenciosamente el mensaje. Siempre haz `await` en las llamadas de conexión, o envuélvelas en un callback de ready como se muestra arriba.
Otro error es usar un solo cliente de Redis tanto para publicar como para suscribirse. Redis requiere una conexión de suscriptor dedicada porque un cliente en modo subscribe no puede ejecutar comandos normales. La llamada `pubClient.duplicate()` en el ejemplo crea esa segunda conexión por ti.
Un problema más sutil es depender de `io.local` para lógica entre servidores. `io.local.emit` solo se dispara en el servidor actual y evita Redis, lo cual es útil para diagnósticos por instancia pero falla en el momento en que asumes que el mensaje llegó a todos los nodos. Usa `io.emit` normal para cualquier cosa que deba llegar a todos los clientes.
Resumen
El escalado horizontal de servidores WebSocket se reduce a dos piezas: un balanceador de carga delante de N instancias de Node.js, y Redis Pub/Sub como bus de mensajes entre ellas. El adaptador `socket.io-redis` envuelve el bus para que el código de la aplicación permanezca sin cambios. Las salas, las difusiones y los namespaces funcionan entre instancias de la misma forma que funcionan en una sola. Las principales preocupaciones operativas son la disponibilidad de Redis, las sticky sessions para los upgrades de WebSocket, y no confundir nunca `io.local.emit` con `io.emit`.
Punto de control de la lección
Repasa lo aprendido y recibe comentarios sobre tu trabajo.