feat(realtime): Socket.IO Redis adapter — enable horizontal scaling

- RedisIoAdapter wires @socket.io/redis-adapter with dedicated pub/sub clients
- channel key 'tripz:sio' isolates on shared Redis
- wired in main.ts via useWebSocketAdapter; multiple app replicas can now share WS connections

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 18:06:24 +03:00
co-authored by Claude Opus 4.8
parent 16df6258c1
commit e0ccd2efb7
4 changed files with 54 additions and 1 deletions
+1
View File
@@ -29,6 +29,7 @@
"@nestjs/throttler": "^6.2.0",
"@nestjs/typeorm": "^11.0.0",
"@nestjs/websockets": "^11.0.0",
"@socket.io/redis-adapter": "^8.3.0",
"bullmq": "^5.12.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
+6
View File
@@ -4,11 +4,17 @@ import { ValidationPipe, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { RedisIoAdapter } from './realtime/redis-io.adapter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const cfg = app.get(ConfigService);
// مُحوّل Socket.IO عبر Redis — يفعّل التوسّع الأفقي (عدة نسخ)
const redisIoAdapter = new RedisIoAdapter(app, cfg);
await redisIoAdapter.connectToRedis();
app.useWebSocketAdapter(redisIoAdapter);
app.setGlobalPrefix('api');
app.useGlobalPipes(
new ValidationPipe({ whitelist: true, transform: true }),
+46
View File
@@ -0,0 +1,46 @@
import { INestApplicationContext, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { IoAdapter } from '@nestjs/platform-socket.io';
import { ServerOptions } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import Redis from 'ioredis';
/**
* مُحوّل Socket.IO مدعوم بـ Redis — يجعل التطبيق قابلاً للتشغيل بعدة نسخ
* (توسّع أفقي) تتشارك اتصالات الويب سوكت عبر Redis. بلا هذا، رسالة من نسخة
* لا تصل زبوناً متصلاً بنسخة أخرى (راجع docs/09).
* مفتاح القنوات ببادئة tripz للعزل عن أي تطبيق آخر على نفس Redis.
*/
export class RedisIoAdapter extends IoAdapter {
private readonly logger = new Logger('SocketRedis');
private adapterConstructor: ReturnType<typeof createAdapter>;
constructor(
app: INestApplicationContext,
private readonly config: ConfigService,
) {
super(app);
}
async connectToRedis(): Promise<void> {
const opts = {
host: this.config.get<string>('redis.host'),
port: this.config.get<number>('redis.port'),
db: this.config.get<number>('redis.db'),
maxRetriesPerRequest: null,
};
const pub = new Redis(opts);
const sub = pub.duplicate();
pub.on('error', (e) => this.logger.warn(`pub: ${e.message}`));
sub.on('error', (e) => this.logger.warn(`sub: ${e.message}`));
// key يعزل قنوات socket.io الخاصة بـ Tripz عن غيرها على نفس Redis
this.adapterConstructor = createAdapter(pub, sub, { key: 'tripz:sio' });
this.logger.log('Socket.IO Redis adapter ready (horizontal scale enabled)');
}
createIOServer(port: number, options?: ServerOptions): any {
const server = super.createIOServer(port, options);
if (this.adapterConstructor) server.adapter(this.adapterConstructor);
return server;
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
## التقنية
- **Socket.IO** عبر `@nestjs/websockets` Gateway.
- **`socket.io-redis-adapter`** للتوسع الأفقي (عدة نسخ من الباك إند تتشارك الحالة).
- **`@socket.io/redis-adapter`** للتوسع الأفقي — **مفعّل** عبر `RedisIoAdapter` في `main.ts` (قنوات ببادئة `tripz:sio`). يسمح بتشغيل عدة نسخ تتشارك اتصالات الويب سوكت عبر Redis.
- **Redis** للحضور (presence) والمطابقة الجغرافية (GEO commands).
## المصادقة على الاتصال