feat: podpora high-availability a multi-replica nasazení

- Socket.io Redis adapter pro sdílený stav přes repliky
- graceful shutdown serveru
- WATCH/MULTI v updateData pro race-condition-safe aktualizace
- lease mechanismus pro push reminder (zabrání duplicitnímu odesílání)
- k8s/ manifesty pro testovací kind cluster
- Dockerfile: opraven EXPOSE port na 3001
- .gitignore: ignorovány Claude pracovní soubory
This commit is contained in:
2026-05-20 17:01:33 +02:00
committed by batmanisko
parent 17132d4124
commit df5423511f
31 changed files with 1252 additions and 510 deletions
+25 -5
View File
@@ -1,12 +1,15 @@
import { DefaultEventsMap, Server } from "socket.io";
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";
let io: Server<DefaultEventsMap, DefaultEventsMap, DefaultEventsMap, any>;
let pubClient: ReturnType<typeof createClient>;
let subClient: ReturnType<typeof createClient>;
export const initWebsocket = (server: any) => {
io = new Server(server, {
cors: {
origin: "*",
},
cors: { origin: "*" },
transports: ["websocket"],
});
io.on("connection", (socket) => {
console.log(`New client connected: ${socket.id}`);
@@ -26,7 +29,24 @@ export const initWebsocket = (server: any) => {
});
});
return io;
}
};
/** Připojí Redis adapter pro cross-pod broadcasting. Volat až po inicializaci Redis klienta. */
export const initRedisAdapter = async () => {
const HOST = process.env.REDIS_HOST ?? 'localhost';
const PORT = process.env.REDIS_PORT ?? 6379;
const url = `redis://${HOST}:${PORT}`;
pubClient = createClient({ url }) as ReturnType<typeof createClient>;
subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient as any, subClient as any));
console.log('Socket.io: Redis adapter connected');
};
/** Zavře pub/sub Redis klienty adaptéru při graceful shutdown. */
export const shutdownWebsocketClients = async () => {
await Promise.allSettled([pubClient?.quit(), subClient?.quit()]);
};
export const getWebsocket = () => io;
@@ -34,4 +54,4 @@ export const getWebsocket = () => io;
export const emitToUser = (login: string, event: string, data: unknown) => {
if (!io) return;
io.to(`user:${login}`).emit(event, data);
}
};