Redis
O Redis é utilizado para múltiplos propósitos no CSGOFlip: sessions, cache, filas de processamento e pub/sub para WebSocket.
Casos de Uso
Configuração
Conexão
typescript
// redis.config.ts
import { RedisOptions } from 'ioredis';
export const redisConfig: RedisOptions = {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
db: 0,
// Retry
retryStrategy: (times: number) => {
if (times > 10) {
return null; // Desistir
}
return Math.min(times * 50, 2000);
},
// Timeouts
connectTimeout: 10000,
commandTimeout: 5000,
// Keep alive
keepAlive: 30000,
// TLS (produção)
tls: process.env.NODE_ENV === 'production' ? {} : undefined,
};Serviço Redis
typescript
// redis.service.ts
@Injectable()
export class RedisService implements OnModuleInit, OnModuleDestroy {
private client: Redis;
constructor() {
this.client = new Redis(redisConfig);
}
async onModuleInit() {
this.client.on('error', (err) => {
console.error('Redis error:', err);
});
this.client.on('connect', () => {
console.log('Redis connected');
});
}
async onModuleDestroy() {
await this.client.quit();
}
// Operações básicas
async get(key: string): Promise<string | null> {
return this.client.get(key);
}
async set(key: string, value: string, ttl?: number): Promise<void> {
if (ttl) {
await this.client.setex(key, ttl, value);
} else {
await this.client.set(key, value);
}
}
async del(key: string): Promise<void> {
await this.client.del(key);
}
async exists(key: string): Promise<boolean> {
return (await this.client.exists(key)) === 1;
}
// Operações de lista
async lpush(key: string, value: string): Promise<void> {
await this.client.lpush(key, value);
}
async lrange(key: string, start: number, stop: number): Promise<string[]> {
return this.client.lrange(key, start, stop);
}
async ltrim(key: string, start: number, stop: number): Promise<void> {
await this.client.ltrim(key, start, stop);
}
// Operações de set
async sadd(key: string, member: string): Promise<void> {
await this.client.sadd(key, member);
}
async srem(key: string, member: string): Promise<void> {
await this.client.srem(key, member);
}
async smembers(key: string): Promise<string[]> {
return this.client.smembers(key);
}
// Operações de hash
async hset(key: string, field: string, value: string): Promise<void> {
await this.client.hset(key, field, value);
}
async hget(key: string, field: string): Promise<string | null> {
return this.client.hget(key, field);
}
async hgetall(key: string): Promise<Record<string, string>> {
return this.client.hgetall(key);
}
// Incremento atômico
async incr(key: string): Promise<number> {
return this.client.incr(key);
}
async decr(key: string): Promise<number> {
return this.client.decr(key);
}
// TTL
async expire(key: string, seconds: number): Promise<void> {
await this.client.expire(key, seconds);
}
async ttl(key: string): Promise<number> {
return this.client.ttl(key);
}
// Pub/Sub
async publish(channel: string, message: string): Promise<void> {
await this.client.publish(channel, message);
}
subscribe(channel: string, callback: (message: string) => void): void {
const subscriber = this.client.duplicate();
subscriber.subscribe(channel);
subscriber.on('message', (ch, message) => {
if (ch === channel) {
callback(message);
}
});
}
}Sessions
typescript
// session.service.ts
@Injectable()
export class SessionService {
private readonly PREFIX = 'session:';
private readonly TTL = 7 * 24 * 60 * 60; // 7 dias
constructor(private redis: RedisService) {}
async create(userId: bigint, metadata: SessionMetadata): Promise<string> {
const sessionId = crypto.randomUUID();
const key = `${this.PREFIX}${sessionId}`;
const session: Session = {
userId: userId.toString(),
createdAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + this.TTL * 1000).toISOString(),
...metadata,
};
await this.redis.set(key, JSON.stringify(session), this.TTL);
// Adicionar à lista de sessões do usuário
await this.redis.sadd(`user:${userId}:sessions`, sessionId);
return sessionId;
}
async validate(sessionId: string): Promise<Session | null> {
const key = `${this.PREFIX}${sessionId}`;
const data = await this.redis.get(key);
if (!data) return null;
const session = JSON.parse(data);
// Atualizar última atividade
session.lastActivity = new Date().toISOString();
await this.redis.set(key, JSON.stringify(session), this.TTL);
return session;
}
async invalidate(sessionId: string): Promise<void> {
const key = `${this.PREFIX}${sessionId}`;
const data = await this.redis.get(key);
if (data) {
const session = JSON.parse(data);
await this.redis.srem(`user:${session.userId}:sessions`, sessionId);
}
await this.redis.del(key);
}
async invalidateAllUserSessions(userId: bigint): Promise<void> {
const sessions = await this.redis.smembers(`user:${userId}:sessions`);
for (const sessionId of sessions) {
await this.redis.del(`${this.PREFIX}${sessionId}`);
}
await this.redis.del(`user:${userId}:sessions`);
}
}Cache
typescript
// cache.service.ts
@Injectable()
export class CacheService {
constructor(private redis: RedisService) {}
async get<T>(key: string): Promise<T | null> {
const data = await this.redis.get(`cache:${key}`);
return data ? JSON.parse(data) : null;
}
async set<T>(key: string, value: T, ttl: number = 300): Promise<void> {
await this.redis.set(`cache:${key}`, JSON.stringify(value), ttl);
}
async invalidate(key: string): Promise<void> {
await this.redis.del(`cache:${key}`);
}
async invalidatePattern(pattern: string): Promise<void> {
const keys = await this.redis.keys(`cache:${pattern}`);
for (const key of keys) {
await this.redis.del(key);
}
}
// Cache decorator helper
async cached<T>(
key: string,
ttl: number,
factory: () => Promise<T>,
): Promise<T> {
const cached = await this.get<T>(key);
if (cached !== null) {
return cached;
}
const value = await factory();
await this.set(key, value, ttl);
return value;
}
}
// Exemplo de uso
async getCases(): Promise<Case[]> {
return this.cacheService.cached(
'cases:active',
300, // 5 minutos
() => this.caseRepository.findActive(),
);
}BullMQ Queues
typescript
// queue.module.ts
import { BullModule } from '@nestjs/bullmq';
@Module({
imports: [
BullModule.forRoot({
connection: {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
},
defaultJobOptions: {
attempts: 3,
backoff: {
type: 'exponential',
delay: 1000,
},
removeOnComplete: 100,
removeOnFail: 1000,
},
}),
BullModule.registerQueue(
{ name: 'payment' },
{ name: 'notification' },
{ name: 'email' },
),
],
})
export class QueueModule {}Processor
typescript
// payment.processor.ts
@Processor('payment')
export class PaymentProcessor {
constructor(
private paymentService: PaymentService,
private logger: Logger,
) {}
@Process('process-withdrawal')
async processWithdrawal(job: Job<{ withdrawalId: string }>) {
this.logger.log(`Processing withdrawal ${job.data.withdrawalId}`);
try {
await this.paymentService.processWithdrawal(job.data.withdrawalId);
return { success: true };
} catch (error) {
this.logger.error(`Withdrawal failed: ${error.message}`);
throw error; // Retry
}
}
@Process('process-deposit')
async processDeposit(job: Job<{ depositId: string }>) {
await this.paymentService.confirmDeposit(job.data.depositId);
return { success: true };
}
}Adicionar Job
typescript
// payment.service.ts
@Injectable()
export class PaymentService {
constructor(
@InjectQueue('payment') private paymentQueue: Queue,
) {}
async scheduleWithdrawalProcessing(withdrawalId: string): Promise<void> {
await this.paymentQueue.add(
'process-withdrawal',
{ withdrawalId },
{
delay: 5000, // 5s delay
priority: 1,
},
);
}
}Distributed Locks (Redlock)
typescript
// redlock.service.ts
import Redlock from 'redlock';
@Injectable()
export class RedlockService implements OnModuleInit {
private redlock: Redlock;
constructor(private redis: RedisService) {}
onModuleInit() {
this.redlock = new Redlock([this.redis.client], {
driftFactor: 0.01,
retryCount: 10,
retryDelay: 200,
retryJitter: 200,
automaticExtensionThreshold: 500,
});
this.redlock.on('error', (error) => {
console.error('Redlock error:', error);
});
}
async using<T>(
resources: string[],
duration: number,
routine: () => Promise<T>,
): Promise<T> {
return this.redlock.using(resources, duration, routine);
}
async acquire(
resources: string[],
duration: number,
): Promise<Lock> {
return this.redlock.acquire(resources, duration);
}
}
// Exemplo de uso
async openCase(userId: bigint, caseId: bigint) {
const lockKey = `user:${userId}:case-open`;
return this.redlock.using([lockKey], 30000, async () => {
// Operação protegida por lock
const balance = await this.getBalance(userId);
// ...
});
}Rate Limiting
typescript
// rate-limit.service.ts
@Injectable()
export class RateLimitService {
constructor(private redis: RedisService) {}
async isRateLimited(
key: string,
limit: number,
windowSeconds: number,
): Promise<boolean> {
const redisKey = `ratelimit:${key}`;
const current = await this.redis.incr(redisKey);
if (current === 1) {
await this.redis.expire(redisKey, windowSeconds);
}
return current > limit;
}
async getRemainingAttempts(
key: string,
limit: number,
): Promise<number> {
const redisKey = `ratelimit:${key}`;
const current = await this.redis.get(redisKey);
return Math.max(0, limit - parseInt(current || '0', 10));
}
}Monitoramento
Métricas
bash
# Info geral
redis-cli INFO
# Memória
redis-cli INFO memory
# Clientes
redis-cli CLIENT LIST
# Slow log
redis-cli SLOWLOG GET 10
# Keys por pattern
redis-cli KEYS "session:*" | wc -lDashboard
typescript
// redis-monitor.service.ts
async getStats(): Promise<RedisStats> {
const info = await this.redis.info();
return {
connectedClients: info.connected_clients,
usedMemory: info.used_memory_human,
totalKeys: info.db0?.keys || 0,
hitRate: info.keyspace_hits / (info.keyspace_hits + info.keyspace_misses),
uptime: info.uptime_in_days,
};
}Configuração de Produção
conf
# redis.conf
# Memória
maxmemory 2gb
maxmemory-policy allkeys-lru
# Persistência
appendonly yes
appendfsync everysec
# Segurança
requirepass your-strong-password
bind 127.0.0.1
# Performance
tcp-keepalive 300
timeout 0
# Logs
loglevel notice
logfile /var/log/redis/redis.log