Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Redis Integration - szybkie magazyny tributów

organizatorze tributyców! Konsul Caesar.js postanowił zmodernizować system przechowywania w aplikacji. Czas na Redis - najbardziej zaawansowany magazyn tributów, który pozwala na błyskawiczne przechowywanie i pobieranie danych w całej legionie!

Czym jest Redis w świecie legionariuszów?

Wyobraź sobie magiczny skarbiec, który:

  • Działa w pamięci - wszystko dostępne natychmiast, bez czekania
  • Synchronizuje się między fortami - każdy legion ma dostęp do tych samych danych
  • Oferuje różne typy magazynów - skrzynie, listy, mapy, kolejki
  • Nie traci danych - może zapisywać stan na dysk
  • Skaluje się poziomo - można dodawać kolejne magazyny

Redis to jak sieci tributyców rozrzucone po wszystkich prowincjach, dostępne dla całego legionu!

Instalacja i konfiguracja Redis

1# Instalacja Redis (Docker)
2docker run --name redis-legionariusze  -p 6379:6379  -d redis:latest redis-server --appendonly yes
3
4# Instalacja modułów NestJS
5npm install redis @nestjs/cache-manager cache-manager-redis-store
1// redis.config.ts
2import { ConfigService } from '@nestjs/config';
3import { redisStore } from 'cache-manager-redis-store';
4
5export const redisConfigFactory = async (configService: ConfigService) => {
6 return {
7 store: redisStore,
8 host: configService.get('REDIS_HOST', 'localhost'),
9 port: configService.get('REDIS_PORT', 6379),
10 password: configService.get('REDIS_PASSWORD'),
11 db: configService.get('REDIS_DB', 0),
12 ttl: configService.get('CACHE_TTL', 300),
13 max: configService.get('CACHE_MAX_ITEMS', 1000),
14 
15 // Dodatkowe opcje Redis
16 retryDelayOnFailover: 100,
17 enableReadyCheck: true,
18 maxRetriesPerRequest: null,
19 lazyConnect: true,
20 
21 // Ustawienia connection poolingu
22 family: 4,
23 keepAlive: true,
24 connectTimeout: 10000,
25 commandTimeout: 5000,
26 };
27};
28
29// redis.module.ts
30import { CacheModule } from '@nestjs/cache-manager';
31import { Module, Global } from '@nestjs/common';
32
33@Global()
34@Module({
35 imports: [
36 CacheModule.registerAsync({
37 imports: [ConfigModule],
38 useFactory: redisConfigFactory,
39 inject: [ConfigService],
40 }),
41 ],
42 exports: [CacheModule],
43})
44export class RedisModule {}

Redis jako Session Store

1// redis-session.service.ts
2import { Injectable, Inject } from '@nestjs/common';
3import { CACHE_MANAGER } from '@nestjs/cache-manager';
4import { Cache } from 'cache-manager';
5
6interface LegionarySession {
7 legionariuszeId: number;
8 userName: string;
9 currentLegion: string;
10 loginTime: Date;
11 lastActivity: Date;
12 permissions: string[];
13 tributeAccess: number[];
14}
15
16@Injectable()
17export class RedisSessionService {
18 constructor(
19 @Inject(CACHE_MANAGER) private cacheManager: Cache,
20 ) {}
21
22 async createSession(sessionId: string, sessionData: LegionarySession): Promise<void> {
23 const sessionKey = `session:${sessionId}`;
24
25 // Sesja wygasa po 24 godzinach
26 await this.cacheManager.set(sessionKey, sessionData, 86400);
27 
28 // Dodaj do indeksu aktywnych sesji
29 await this.addToActiveSessions(sessionData.legionariuszeId, sessionId);
30 
31 console.log(` Sesja utworzona dla legionariusza: ${sessionData.userName}`);
32 }
33
34 async getSession(sessionId: string): Promise<LegionarySession | null> {
35 const sessionKey = `session:${sessionId}`;
36 const session = await this.cacheManager.get<LegionarySession>(sessionKey);
37 
38 if (session) {
39 // Aktualizuj ostatnią aktywność
40 session.lastActivity = new Date();
41 await this.cacheManager.set(sessionKey, session, 86400);
42 }
43 
44 return session;
45 }
46
47 async updateSession(sessionId: string, updates: Partial<LegionarySession>): Promise<void> {
48 const session = await this.getSession(sessionId);
49 if (!session) return;
50
51 const updatedSession = { ...session, ...updates };
52 const sessionKey = `session:${sessionId}`;
53 
54 await this.cacheManager.set(sessionKey, updatedSession, 86400);
55 }
56
57 async destroySession(sessionId: string): Promise<void> {
58 const session = await this.getSession(sessionId);
59 const sessionKey = `session:${sessionId}`;
60 
61 if (session) {
62 await this.removeFromActiveSessions(session.legionariuszeId, sessionId);
63 }
64 
65 await this.cacheManager.del(sessionKey);
66 console.log(`Sesja zakończona: ${sessionId}`);
67 }
68
69 async getActiveSessions(legionariuszeId: number): Promise<string[]> {
70 const key = `active_sessions:${legionariuszeId}`;
71 const sessions = await this.cacheManager.get<string[]>(key);
72 return sessions || [];
73 }
74
75 private async addToActiveSessions(legionariuszeId: number, sessionId: string): Promise<void> {
76 const key = `active_sessions:${legionariuszeId}`;
77 const sessions = await this.getActiveSessions(legionariuszeId);
78 
79 if (!sessions.includes(sessionId)) {
80 sessions.push(sessionId);
81 await this.cacheManager.set(key, sessions, 86400);
82 }
83 }
84
85 private async removeFromActiveSessions(legionariuszeId: number, sessionId: string): Promise<void> {
86 const key = `active_sessions:${legionariuszeId}`;
87 const sessions = await this.getActiveSessions(legionariuszeId);
88 
89 const filteredSessions = sessions.filter(s => s !== sessionId);
90 if (filteredSessions.length > 0) {
91 await this.cacheManager.set(key, filteredSessions, 86400);
92 } else {
93 await this.cacheManager.del(key);
94 }
95 }
96
97 // Czyszczenie nieaktywnych sesji
98 @Cron('0 */15 * * * *') // Co 15 minut
99 async cleanupInactiveSessions(): Promise<void> {
100 // Ta funkcja wymagałaby bezpośredniego dostępu do Redis
101 // aby iterować po wszystkich kluczach session:*
102 console.log('🧹 Sprzątanie nieaktywnych sesji...');
103 }
104}

Redis Pub/Sub dla komunikacji między fortami

1// redis-pubsub.service.ts
2import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
3import { ConfigService } from '@nestjs/config';
4import Redis from 'ioredis';
5
6@Injectable()
7export class RedisPubSubService implements OnModuleInit, OnModuleDestroy {
8 private publisher: Redis;
9 private subscriber: Redis;
10 private eventHandlers = new Map<string, Function[]>();
11
12 constructor(private configService: ConfigService) {}
13
14 async onModuleInit() {
15 const redisConfig = {
16 host: this.configService.get('REDIS_HOST', 'localhost'),
17 port: this.configService.get('REDIS_PORT', 6379),
18 password: this.configService.get('REDIS_PASSWORD'),
19 retryDelayOnFailover: 100,
20 enableReadyCheck: true,
21 };
22
23 this.publisher = new Redis(redisConfig);
24 this.subscriber = new Redis(redisConfig);
25
26 this.subscriber.on('message', (channel, message) => {
27 this.handleMessage(channel, message);
28 });
29
30 console.log('Redis Pub/Sub połączony!');
31 }
32
33 async onModuleDestroy() {
34 await this.publisher.quit();
35 await this.subscriber.quit();
36 }
37
38 // Wysyłanie wiadomości do całego legionu
39 async publishLegionMessage(event: string, data: any): Promise<void> {
40 const channel = `legion::${event}`;
41 const message = JSON.stringify({
42 timestamp: new Date().toISOString(),
43 event,
44 data,
45 source: 'legion-command',
46 });
47
48 await this.publisher.publish(channel, message);
49 console.log(`Wiadomość wysłana do legionu: ${event}`);
50 }
51
52 // Wysyłanie wiadomości do konkretnej kohorty
53 async publishLegionMessage(cohortId: string, event: string, data: any): Promise<void> {
54 const channel = `cohort:${cohortId}:${event}`;
55 const message = JSON.stringify({
56 timestamp: new Date().toISOString(),
57 event,
58 data,
59 targetLegion: cohortId,
60 });
61
62 await this.publisher.publish(channel, message);
63 console.log(`Wiadomość wysłana do kohorty ${cohortId}: ${event}`);
64 }
65
66 // Nasłuchiwanie na określone eventy
67 async subscribe(pattern: string, handler: (event: string, data: any) => void): Promise<void> {
68 await this.subscriber.psubscribe(pattern);
69 
70 if (!this.eventHandlers.has(pattern)) {
71 this.eventHandlers.set(pattern, []);
72 }
73 this.eventHandlers.get(pattern)!.push(handler);
74 }
75
76 private handleMessage(channel: string, message: string): void {
77 try {
78 const parsedMessage = JSON.parse(message);
79 
80 // Znajdź wszystkie pasujące handlery
81 this.eventHandlers.forEach((handlers, pattern) => {
82 if (this.matchPattern(channel, pattern)) {
83 handlers.forEach(handler => {
84 try {
85 handler(parsedMessage.event, parsedMessage.data);
86 } catch (error) {
87 console.error(`Błąd w handlerze dla ${channel}:`, error);
88 }
89 });
90 }
91 });
92 } catch (error) {
93 console.error('Błąd parsowania wiadomości:', error);
94 }
95 }
96
97 private matchPattern(channel: string, pattern: string): boolean {
98 // Prosta implementacja dopasowania wzorców
99 const regexPattern = pattern.replace('*', '.*').replace('?', '.');
100 const regex = new RegExp(`^${regexPattern}$`);
101 return regex.test(channel);
102 }
103}
104
105// Użycie w serwisie
106@Injectable()
107export class LegionCommunicationService implements OnModuleInit {
108 constructor(private pubSub: RedisPubSubService) {}
109
110 async onModuleInit() {
111 // Nasłuchuj na eventy legionu
112 await this.pubSub.subscribe('legion::*', (event, data) => {
113 this.handleLegionEvent(event, data);
114 });
115
116 // Nasłuchuj na eventy konkretnej kohorty
117 const myLegionId = process.env.COHORT_ID || 'praetoria';
118 await this.pubSub.subscribe(`cohort:${myLegionId}:*`, (event, data) => {
119 this.handleLegionEvent(event, data);
120 });
121 }
122
123 async announceTributeFound(tributeData: any): Promise<void> {
124 await this.pubSub.publishLegionMessage('tribute_found', {
125 tribute: tributeData,
126 location: tributeData.coordinates,
127 finder: tributeData.discoveredBy,
128 timestamp: new Date(),
129 });
130 }
131
132 async requestBackup(cohortId: string, coordinates: any): Promise<void> {
133 await this.pubSub.publishLegionMessage('backup_request', {
134 requestingLegion: cohortId,
135 location: coordinates,
136 urgency: 'high',
137 reason: 'Enemy cohorts detected',
138 });
139 }
140
141 async sendDirectOrder(targetLegionId: string, order: any): Promise<void> {
142 await this.pubSub.publishLegionMessage(targetLegionId, 'direct_order', {
143 order: order.type,
144 details: order.details,
145 deadline: order.deadline,
146 priority: order.priority,
147 });
148 }
149
150 private handleLegionEvent(event: string, data: any): void {
151 switch (event) {
152 case 'tribute_found':
153 console.log(` Nowy tributy znaleziony: ${data.tribute.name}`);
154 this.notifyLegionAboutTribute(data);
155 break;
156 
157 case 'backup_request':
158 console.log(`Prośba o wsparcie od ${data.requestingLegion}`);
159 this.evaluateBackupRequest(data);
160 break;
161 
162 case 'weather_alert':
163 console.log(` Alert pogodowy: ${data.severity}`);
164 this.handleWeatherAlert(data);
165 break;
166 }
167 }
168
169 private handleLegionEvent(event: string, data: any): void {
170 switch (event) {
171 case 'direct_order':
172 console.log(` Nowy rozkaz: ${data.order}`);
173 this.processDirectOrder(data);
174 break;
175 
176 case 'supply_request':
177 console.log(` Prośba o zaopatrzenie: ${data.supplies}`);
178 this.handleSupplyRequest(data);
179 break;
180 }
181 }
182}

Redis Lists dla kolejek zadań

1// redis-queue.service.ts
2@Injectable()
3export class RedisQueueService {
4 constructor(
5 @Inject(CACHE_MANAGER) private cacheManager: Cache,
6 ) {}
7
8 // Dodaj zadanie do kolejki
9 async enqueueTask(queueName: string, task: any): Promise<void> {
10 const queueKey = `queue:${queueName}`;
11 const taskData = {
12 id: `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
13 ...task,
14 enqueuedAt: new Date().toISOString(),
15 };
16
17 // Używamy Redis LPUSH (dodaj na początek listy)
18 await this.pushToQueue(queueKey, taskData);
19 console.log(` Zadanie dodane do kolejki ${queueName}: ${taskData.id}`);
20 }
21
22 // Pobierz zadanie z kolejki (FIFO)
23 async dequeueTask(queueName: string): Promise<any | null> {
24 const queueKey = `queue:${queueName}`;
25 
26 // RPOP - usuń z końca listy (FIFO)
27 const taskData = await this.popFromQueue(queueKey);
28 
29 if (taskData) {
30 console.log(`✅ Pobrano zadanie z kolejki ${queueName}: ${taskData.id}`);
31 }
32 
33 return taskData;
34 }
35
36 // Sprawdź długość kolejki
37 async getQueueLength(queueName: string): Promise<number> {
38 const queueKey = `queue:${queueName}`;
39 // Tu potrzebowalibyśmy bezpośredniej komendy Redis LLEN
40 return 0; // placeholder
41
42 // Pobierz wszystkie zadania bez usuwania
43 async peekQueue(queueName: string, limit: number = 10): Promise<any[]> {
44 const queueKey = `queue:${queueName}`;
45 // Tu potrzebowalibyśmy bezpośredniej komendy Redis LRANGE
46 return []; // placeholder
47
48 // Implementacje pomocnicze (wymagają bezpośredniego Redis)
49 private async pushToQueue(queueKey: string, data: any): Promise<void> {
50 // W rzeczywistości użylibyśmy: redis.lpush(queueKey, JSON.stringify(data))
51 const currentQueue = await this.cacheManager.get<any[]>(queueKey) || [];
52 currentQueue.unshift(data);
53 await this.cacheManager.set(queueKey, currentQueue);
54 }
55
56 private async popFromQueue(queueKey: string): Promise<any | null> {
57 // W rzeczywistości użylibyśmy: redis.rpop(queueKey)
58 const currentQueue = await this.cacheManager.get<any[]>(queueKey) || [];
59 const task = currentQueue.pop();
60 await this.cacheManager.set(queueKey, currentQueue);
61 return task || null;
62 }
63}
64
65// Specialized queues
66@Injectable()
67export class TributeQueueService extends RedisQueueService {
68 async addTributeHuntTask(huntData: any): Promise<void> {
69 await this.enqueueTask('tribute_hunts', {
70 type: 'tribute_hunt',
71 priority: huntData.resourceValue > 10000 ? 'high' : 'normal',
72 ...huntData,
73 });
74 }
75
76 async addMapAnalysisTask(mapData: any): Promise<void> {
77 await this.enqueueTask('map_analysis', {
78 type: 'map_analysis',
79 priority: 'normal',
80 ...mapData,
81 });
82 }
83
84 async getNextTributeTask(): Promise<any> {
85 return await this.dequeueTask('tribute_hunts');
86 }
87
88 async getNextMapTask(): Promise<any> {
89 return await this.dequeueTask('map_analysis');
90 }
91}

Redis Sets dla unikalnych kolekcji

1// redis-sets.service.ts
2@Injectable()
3export class RedisSetsService {
4 constructor(
5 @Inject(CACHE_MANAGER) private cacheManager: Cache,
6 ) {}
7
8 // Zarządzanie aktywnym legionem kohorty
9 async addMemberToLegion(cohortId: string, legionariuszeId: string): Promise<void> {
10 const setKey = `cohort:${cohortId}:legion`;
11 const currentLegion = await this.cacheManager.get<Set<string>>(setKey) || new Set();
12 
13 if (currentLegion instanceof Set) {
14 currentLegion.add(legionariuszeId);
15 } else {
16 // Jeśli dane nie są Set, konwertuj
17 const newSet = new Set(Array.isArray(currentLegion) ? currentLegion : []);
18 newSet.add(legionariuszeId);
19 await this.cacheManager.set(setKey, newSet);
20 return;
21 }
22 
23 await this.cacheManager.set(setKey, currentLegion);
24 console.log(`Legionariusz ${legionariuszeId} dodany do legionu kohorty ${cohortId}`);
25 }
26
27 async removeMemberFromLegion(cohortId: string, legionariuszeId: string): Promise<void> {
28 const setKey = `cohort:${cohortId}:legion`;
29 const currentLegion = await this.cacheManager.get<Set<string>>(setKey) || new Set();
30 
31 if (currentLegion instanceof Set) {
32 currentLegion.delete(legionariuszeId);
33 await this.cacheManager.set(setKey, currentLegion);
34 }
35 
36 console.log(`Legionariusz ${legionariuszeId} usunięty z legionu kohorty ${cohortId}`);
37 }
38
39 async getLegions(cohortId: string): Promise<string[]> {
40 const setKey = `cohort:${cohortId}:legion`;
41 const legion = await this.cacheManager.get<Set<string>>(setKey) || new Set();
42 return Array.from(legion);
43 }
44
45 async isLegionMember(cohortId: string, legionariuszeId: string): Promise<boolean> {
46 const legion = await this.getLegions(cohortId);
47 return legion.includes(legionariuszeId);
48 }
49
50 // Zarządzanie odwiedzonymi wyspami
51 async markIslandVisited(legionId: string, islandId: string): Promise<void> {
52 const setKey = `legion:${legionId}:visited_islands`;
53 const visitedIslands = await this.cacheManager.get<Set<string>>(setKey) || new Set();
54 
55 if (visitedIslands instanceof Set) {
56 visitedIslands.add(islandId);
57 } else {
58 const newSet = new Set(Array.isArray(visitedIslands) ? visitedIslands : []);
59 newSet.add(islandId);
60 await this.cacheManager.set(setKey, newSet);
61 return;
62 }
63 
64 await this.cacheManager.set(setKey, visitedIslands);
65 console.log(`Wyspa ${islandId} oznaczona jako odwiedzona przez legion ${legionId}`);
66 }
67
68 async getVisitedIslands(legionId: string): Promise<string[]> {
69 const setKey = `legion:${legionId}:visited_islands`;
70 const islands = await this.cacheManager.get<Set<string>>(setKey) || new Set();
71 return Array.from(islands);
72 }
73
74 async hasVisitedIsland(legionId: string, islandId: string): Promise<boolean> {
75 const visitedIslands = await this.getVisitedIslands(legionId);
76 return visitedIslands.includes(islandId);
77 }
78
79 // Operacje na zbiorach - przecięcia, różnice
80 async getCommonLegionMembers(cohortId1: string, cohortId2: string): Promise<string[]> {
81 const legion1 = new Set(await this.getLegions(cohortId1));
82 const legion2 = new Set(await this.getLegions(cohortId2));
83 
84 const intersection = new Set([...legion1].filter(x => legion2.has(x)));
85 return Array.from(intersection);
86 }
87
88 async getLegionDifference(cohortId1: string, cohortId2: string): Promise<string[]> {
89 const legion1 = new Set(await this.getLegions(cohortId1));
90 const legion2 = new Set(await this.getLegions(cohortId2));
91 
92 const difference = new Set([...legion1].filter(x => !legion2.has(x)));
93 return Array.from(difference);
94 }
95}

Redis Health Check i Monitoring

1// redis-health.service.ts
2@Injectable()
3export class RedisHealthService {
4 constructor(
5 @Inject(CACHE_MANAGER) private cacheManager: Cache,
6 ) {}
7
8 async checkRedisHealth(): Promise<{
9 status: 'healthy' | 'unhealthy';
10 latency: number;
11 memoryUsage?: string;
12 connectedClients?: number;
13 error?: string;
14 }> {
15 const startTime = Date.now();
16 
17 try {
18 // Test basic connectivity
19 const testKey = 'health_check_' + Date.now();
20 const testValue = 'ping';
21 
22 await this.cacheManager.set(testKey, testValue, 60);
23 const retrieved = await this.cacheManager.get(testKey);
24 await this.cacheManager.del(testKey);
25 
26 if (retrieved !== testValue) {
27 throw new Error('Redis read/write test failed');
28 }
29 
30 const latency = Date.now() - startTime;
31 
32 return {
33 status: 'healthy',
34 latency,
35 // W prawdziwej implementacji dodalibyśmy więcej metryk
36 };
37 
38 } catch (error) {
39 return {
40 status: 'unhealthy',
41 latency: Date.now() - startTime,
42 error: error.message,
43 };
44 }
45 }
46
47 @Cron('*/30 * * * * *') // Co 30 sekund
48 async monitorRedisHealth(): Promise<void> {
49 const health = await this.checkRedisHealth();
50 
51 if (health.status === 'unhealthy') {
52 console.error(' Redis nie działa!', health.error);
53 // Tu możesz dodać alerty, np. webhook do Slack
54 } else if (health.latency > 1000) {
55 console.warn(` Redis działa wolno: ${health.latency}ms`);
56 }
57 }
58
59 async getRedisStats(): Promise<any> {
60 // W prawdziwej implementacji użylibyśmy Redis INFO command
61 return {
62 uptime: '24 hours',
63 memoryUsage: '256MB',
64 connectedClients: 42,
65 totalConnections: 1337,
66 keyspaceHits: 98765,
67 keyspaceMisses: 1234,
68 };
69 }
70}

Redis Configuration Best Practices

1// redis-advanced.config.ts
2export const redisAdvancedConfig = {
3 // Connection settings
4 host: process.env.REDIS_HOST || 'localhost',
5 port: parseInt(process.env.REDIS_PORT) || 6379,
6 password: process.env.REDIS_PASSWORD,
7 db: parseInt(process.env.REDIS_DB) || 0,
8 
9 // Performance settings
10 maxRetriesPerRequest: 3,
11 retryDelayOnFailover: 100,
12 enableReadyCheck: true,
13 lazyConnect: true,
14 
15 // Memory management
16 maxmemory: '256mb',
17 'maxmemory-policy': 'allkeys-lru',
18 
19 // Persistence
20 save: '900 1 300 10 60 10000', // RDB snapshots
21 appendonly: 'yes', // AOF persistence
22 'appendfsync': 'everysec',
23 
24 // Networking
25 'tcp-keepalive': 300,
26 timeout: 300,
27 
28 // Security
29 requirepass: process.env.REDIS_PASSWORD,
30 'protected-mode': 'yes',
31 
32 // Logging
33 loglevel: 'notice',
34 syslog: 'yes',
35};

Redis to potężne narzędzie, które przekształci twoją aplikację w nowoczesny legion! Dzięki Redis możesz budować skalowalną, wydajną aplikację, która radzi sobie z tysiącami równoczesnych legionistów.

Ir a CodeWorlds