Realtime schalen met een Redis Pub/Sub-adapter
Synchroniseer socketstatus tussen meerdere instances met de Redis IoAdapter voor horizontale schaalbaarheid.
Realtime schalen met een Redis Pub/Sub-adapter is een gratis Enterprise-backend-API's met NestJS-les op CoddyKit. Dit is les 4 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Enterprise-backend-API's met NestJS. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Enterprise-backend-API's met NestJS bevat in totaal 4 lessen.
Het probleem met meerdere instanties
Een enkele NestJS WebSocket-gateway bewaart elke verbonden socket in het geheugen van één Node-proces. Zodra je horizontaal schaalt achter een load balancer, gaat die aanname niet meer op.
- Client A maakt verbinding met instantie 1.
- Client B maakt verbinding met instantie 2.
- Wanneer instantie 1 naar een ruimte uitzendt, krijgt instantie 2 daar niets van mee — dus mist client B het bericht.
Om dit op te lossen hebben we een gedeelde berichtenbus nodig waarmee elke instantie gebeurtenissen naar alle andere instanties kan uitzenden. Redis Pub/Sub is de gebruikelijke keuze en Socket.IO levert hiervoor precies een adapter.
Hoe de Redis-adapter werkt
De @socket.io/redis-adapter vervangt de adapter in het geheugen van Socket.IO. Telkens wanneer je code server.to(room).emit(...) aanroept, publiceert de adapter die gebeurtenis naar een Redis-kanaal in plaats van deze alleen lokaal af te leveren.
- Elke instantie abonneert zich op dezelfde Redis-kanalen.
- Een uitzending op instantie 1 wordt naar Redis gepubliceerd. Daarna ontvangt elke geabonneerde instantie (waaronder instantie 2) de uitzending en levert deze af bij de eigen lokale sockets.
- Redis is alleen een doorgeefluik — socketverbindingen blijven op elk knooppunt bestaan; er wordt geen socketstatus opgeslagen in Redis.
Dit betekent dat lidmaatschap van ruimtes, uitzendingen en zelfs server.emit() correct door het hele cluster worden verspreid.
De onderdelen installeren
Je hebt drie pakketten nodig: een Redis-client (ioredis of de officiële redis-client), de Redis-adapter van Socket.IO en Socket.IO zelf (dat al wordt meegeleverd door @nestjs/platform-socket.io).
De adapter heeft twee Redis-verbindingen nodig: één pubClient voor publiceren en één subClient voor abonneren. Een verbinding in abonnementsmodus kan geen normale opdrachten uitvoeren. Daarom moeten ze gescheiden zijn.
// package.json dependencies (excerpt)
{
"dependencies": {
"@nestjs/platform-socket.io": "^11.0.0",
"@nestjs/websockets": "^11.0.0",
"@socket.io/redis-adapter": "^8.3.0",
"socket.io": "^4.7.0",
"ioredis": "^5.4.0"
}
}Een aangepaste IoAdapter
NestJS verpakt Socket.IO achter een IoAdapter-klasse. Om de Redis-adapter te injecteren, maak je een subklasse van IoAdapter en overschrijf je createIOServer, zodat elke nameserver de Redis-adapter krijgt via server.adapter(...).
connectToRedis()maakt de gedupliceerde publicatie- en abonnementsclients één keer aan tijdens het opstarten.createIOServer()koppelt de adapter die is gemaakt metcreateAdapter(pubClient, subClient).
Dit is het centrale onderdeel van de hele les.
// redis-io.adapter.ts
import { IoAdapter } from '@nestjs/platform-socket.io';
import { ServerOptions } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { Redis } from 'ioredis';
export class RedisIoAdapter extends IoAdapter {
private adapterConstructor: ReturnType<typeof createAdapter>;
async connectToRedis(url: string): Promise<void> {
const pubClient = new Redis(url);
const subClient = pubClient.duplicate();
this.adapterConstructor = createAdapter(pubClient, subClient);
}
createIOServer(port: number, options?: ServerOptions): any {
const server = super.createIOServer(port, options);
server.adapter(this.adapterConstructor);
return server;
}
}Dit koppelen tijdens het opstarten
De adapter moet voordat de app begint te luisteren met Redis zijn verbonden en worden geregistreerd met app.useWebSocketAdapter(). Doe dit in main.ts.
- Maak
RedisIoAdapteraan met de app-instantie. - Gebruik
awaitvoor de Redis-verbinding, zodat een fout het opstarten netjes afbreekt. - Registreer de adapter en roep daarna
app.listen()aan.
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { RedisIoAdapter } from './redis-io.adapter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const redisIoAdapter = new RedisIoAdapter(app);
await redisIoAdapter.connectToRedis(
process.env.REDIS_URL ?? 'redis://localhost:6379',
);
app.useWebSocketAdapter(redisIoAdapter);
await app.listen(3000);
}
bootstrap();De gateway blijft ongewijzigd
Het mooie van deze aanpak: je @WebSocketGateway-code verandert helemaal niet. Je blijft server.to(room).emit() gebruiken en de adapter verspreidt de uitzending transparant over alle instanties.
- Voeg zoals gewoonlijk een client toe aan een ruimte met
client.join(room). - Zend uit met
this.server.to(room).emit(event, payload).
Dezelfde gateway werkt nu zowel met 1 als met 50 replica's.
// chat.gateway.ts
import {
WebSocketGateway,
WebSocketServer,
SubscribeMessage,
MessageBody,
ConnectedSocket,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway({ cors: { origin: '*' } })
export class ChatGateway {
@WebSocketServer() server: Server;
@SubscribeMessage('joinRoom')
onJoin(@ConnectedSocket() client: Socket, @MessageBody() room: string) {
client.join(room);
return { joined: room };
}
@SubscribeMessage('sendMessage')
onMessage(@MessageBody() data: { room: string; text: string }) {
// Fans out to every instance thanks to the Redis adapter
this.server.to(data.room).emit('message', data.text);
}
}Sticky sessions versus polling
De Redis-adapter lost het uitzenden op, maar niet de HTTP-handshake met lang pollen. Bij meerdere instanties verstuurt het polling-transport van Socket.IO verschillende HTTP-aanvragen tijdens de upgrade. Als die aanvragen bij verschillende instanties terechtkomen, mislukt de handshake.
- Optie A: Schakel sticky sessions in op de load balancer, zodat een client altijd dezelfde instantie bereikt.
- Optie B: Dwing alleen het WebSocket-transport af en sla polling volledig over.
De meeste productieopstellingen schakelen sticky sessions in op de ingress- of load-balancerlaag.
// Force WebSocket-only to sidestep multi-request polling handshakes
@WebSocketGateway({
transports: ['websocket'],
cors: { origin: '*' },
})
export class ChatGateway {}De berichtenbus netjes opnieuw verbinden
Als Redis uitvalt, kan de adapter geen gebeurtenissen tussen instanties doorgeven. Met ioredis krijg je standaard automatisch opnieuw verbinden, maar je moet fouten loggen en monitoren, zodat je weet wanneer uitzendingen minder betrouwbaar zijn.
- Voeg listeners toe aan zowel de publicatie- als de abonnementsclient.
- Met een
retryStrategykun je de back-off begrenzen en voorkomen dat Redis wordt overspoeld met nieuwe pogingen.
// redis-io.adapter.ts (hardened connect)
async connectToRedis(url: string): Promise<void> {
const pubClient = new Redis(url, {
retryStrategy: (times) => Math.min(times * 100, 3000),
});
const subClient = pubClient.duplicate();
for (const client of [pubClient, subClient]) {
client.on('error', (err) => console.error('Redis adapter error', err));
client.on('reconnecting', () => console.warn('Redis adapter reconnecting'));
}
this.adapterConstructor = createAdapter(pubClient, subClient);
}Redeneren over de kosten van verspreiding
Elke uitzending tussen instanties wordt Redis-verkeer. Als je begrijpt hoe de verspreiding werkt, kun je Redis beter dimensioneren. Een eenvoudige berekening zonder afhankelijkheden maakt het schaalgedrag concreet: als elke uitzending N instanties moet bereiken, geeft Redis ongeveer één publicatie door die door N−1 andere instanties wordt verwerkt.
Het onderstaande fragment is een eenvoudig TypeScript-programma dat het aantal gepubliceerde berichten per seconde in een cluster schat — er is geen framework of Redis nodig om het uit te voeren.
// Estimate Redis pub/sub relay load for a cluster
function relayLoad(
instances: number,
broadcastsPerSecPerInstance: number,
): { publishes: number; deliveries: number } {
const publishes = instances * broadcastsPerSecPerInstance;
// each publish is consumed by the other (instances - 1) nodes
const deliveries = publishes * (instances - 1);
return { publishes, deliveries };
}
const scenarios = [
{ instances: 2, rate: 100 },
{ instances: 10, rate: 100 },
{ instances: 50, rate: 100 },
];
for (const s of scenarios) {
const { publishes, deliveries } = relayLoad(s.instances, s.rate);
console.log(
`${s.instances} instances -> ${publishes} publishes/s, ${deliveries} deliveries/s`,
);
}Specifieke sockets op verschillende knooppunten benaderen
Naast ruimtes ondersteunt de Redis-adapter ook clusterbrede bewerkingen die Socket.IO beschikbaar maakt op het serverobject:
server.fetchSockets()geeft socketinformatie van alle instanties terug.server.in(socketId).disconnectSockets()kan een socket verbreken die op een ander knooppunt actief is.server.socketsJoin(room)/socketsLeave(room)verplaatsen sockets in het hele cluster.
Deze bewerkingen zijn asynchroon, omdat ze via Redis heen en weer gaan om externe instanties te bereiken.
// admin.gateway.ts
@SubscribeMessage('kick')
async onKick(@MessageBody() socketId: string) {
// Works even if that socket is connected to another instance
const remoteSockets = await this.server.in(socketId).fetchSockets();
for (const s of remoteSockets) {
s.emit('kicked', { reason: 'admin action' });
s.disconnect(true);
}
}Redis Pub/Sub versus de Streams-adapter
Socket.IO biedt twee op Redis gebaseerde adapters met verschillende leveringsgaranties:
- Pub/Sub-adapter (
@socket.io/redis-adapter): verzenden en vergeten. Als een instantie kort de verbinding met Redis verliest, mist deze gedurende die periode berichten. De laagste latentie en de eenvoudigste optie. - Streams-adapter (
@socket.io/redis-streams-adapter): gebruikt Redis Streams, zodat een instantie die opnieuw verbinding maakt gemiste berichten kan afspelen. Iets meer overhead, maar beter geschikt wanneer levering minstens één keer vereist is.
Voor de meeste chat- en meldingenuitzendingen is de Pub/Sub-adapter de standaardaanbeveling. Kies Streams wanneer gemiste uitzendingen tijdens een korte storing onaanvaardbaar zijn.
Korte controle: waarom twee clients?
De Redis-adapter vereist een afzonderlijke pubClient en subClient. Waarom kun je niet één Redis-verbinding voor beide gebruiken?
Samenvatting
Je hebt geleerd hoe je een NestJS-realtimeapp horizontaal schaalt met een Redis Pub/Sub-adapter:
- De Socket.IO-status in het geheugen wordt niet gedeeld tussen instanties — Redis Pub/Sub stuurt broadcasts door het cluster.
- Maak een subklasse van
IoAdapter, verbind een pubClient en subClient, en koppelcreateAdapter(...)increateIOServer. - Registreer de adapter in
main.tsmetapp.useWebSocketAdapter()vóórlisten(); je gatewaycode blijft ongewijzigd. - Schakel sticky sessions in (of forceer het WebSocket-transport), zodat de polling-handshake de loadbalancing overleeft.
- Clusterbrede bewerkingen zoals
fetchSockets()endisconnectSockets()maken een heen-en-terugverbinding via Redis. - Kies in plaats daarvan de Streams-adapter wanneer je broadcasts wilt kunnen afspelen die tijdens een Redis-storing zijn gemist.
Leer TypeScript met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 20
- Lessen
- 76
Veelgestelde vragen
Is de les “Realtime schalen met een Redis Pub/Sub-adapter” gratis?
Ja — de volledige tekst van “Realtime schalen met een Redis Pub/Sub-adapter” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Enterprise-backend-API's met NestJS wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Enterprise-backend-API's met NestJS bevat in totaal 4 lessen.
Wat leer ik in “Realtime schalen met een Redis Pub/Sub-adapter”?
Synchroniseer socketstatus tussen meerdere instances met de Redis IoAdapter voor horizontale schaalbaarheid. Je oefent met Enterprise-backend-API's met NestJS door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met Enterprise-backend-API's met NestJS te beginnen?
Ervaring vooraf is niet nodig. Enterprise-backend-API's met NestJS op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 4 van 4.
Hoe lang duurt de les “Realtime schalen met een Redis Pub/Sub-adapter”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over Enterprise-backend-API's met NestJS?
Ja. Elke les over Enterprise-backend-API's met NestJS bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- WebSocket-gateways met de Socket.IO-adapter
- Socketverbindingen authenticeren en beveiligen
- Server-Sent Events voor eenrichtingspush
- Realtime schalen met een Redis Pub/Sub-adapter