Redis Use Cases: Caching, Queues und Rate Limiting
Sie haben eine Web-App erstellt, die mit einigen Benutzern gut funktioniert, aber mit steigendem Traffic bemerken Sie langsame Seitenladezeiten, doppelte Hintergrundjobs und APIs, die von missbräuchlichen Clients überlastet werden. Dies sind klassische Skalierungsprobleme, die Redis elegant lösen kann. Redis ist ein In-Memory-Datenspeicher, der sich für drei Kernaufgaben hervorragend eignet: Caching, Queues und Rate Limiting. In diesem Artikel erkunden wir jeden Use Case mit praktischen Beispielen und Best Practices.
Warum Redis für diese Use Cases?
Redis speichert Daten im Speicher, was es unglaublich schnell macht – oft mit Antwortzeiten im Sub-Millisekunden-Bereich. Es unterstützt reichhaltige Datenstrukturen wie Strings, Listen, Sets, Sorted Sets und Hashes, die natürlich auf Caching, Queues und Rate Limiting abbilden. Es bietet auch atomare Operationen, Pub/Sub und Persistenzoptionen, was es zu einem vielseitigen Werkzeug für Backend-Entwickler macht.
1. Caching mit Redis
Caching ist der häufigste Redis-Use-Case. Indem Sie häufig abgerufene Daten in Redis speichern, reduzieren Sie die Datenbanklast und beschleunigen Antworten.
Wie Caching funktioniert
Wenn eine Anfrage eingeht, prüfen Sie, ob die Daten in Redis vorhanden sind. Wenn ja (Cache-Hit), geben Sie sie direkt zurück. Wenn nicht (Cache-Miss), holen Sie sie aus der Datenbank, speichern Sie sie in Redis mit einer Ablaufzeit und geben Sie sie zurück. Dieses Muster wird Cache-Aside genannt.
// Node.js Beispiel mit ioredis
const Redis = require('ioredis');
const redis = new Redis();
async function getUser(userId) {
const cacheKey = `user:${userId}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600); // 1 Stunde TTL
return user;
}
Best Practices für Caching
- Setzen Sie eine TTL: Legen Sie immer eine Ablaufzeit fest, um veraltete Daten und Speicherbloat zu vermeiden.
- Verwenden Sie geeignete Datenstrukturen: Für einfache Key-Value verwenden Sie Strings. Für Objekte verwenden Sie Hashes, um Felder einzeln zu aktualisieren.
- Behandeln Sie Cache-Invalidierung: Aktualisieren oder löschen Sie Cache-Einträge, wenn sich die zugrunde liegenden Daten ändern.
- Überwachen Sie die Hit-Rate: Eine niedrige Hit-Rate bedeutet, dass Ihr Cache nicht effektiv ist; passen Sie Keys oder TTLs an.
Cache-Invalidierungsstrategien
Invalidierung ist schwierig. Häufige Strategien sind:
- Zeitbasiert: Verlassen Sie sich auf TTL. Einfach, kann aber veraltete Daten liefern.
- Write-Through: Aktualisieren Sie den Cache, wann immer Sie die Datenbank aktualisieren.
- Write-Behind: Aktualisieren Sie zuerst den Cache und dann asynchron die Datenbank (Risiko von Datenverlust).
2. Queues mit Redis
Queues entkoppeln zeitaufwändige Aufgaben vom Request-Response-Zyklus. Zum Beispiel können das Senden von E-Mails, die Verarbeitung von Bildern oder die Generierung von Berichten asynchron erfolgen.
Implementierung einer einfachen Queue
Redis-Listen sind perfekt für Queues. Verwenden Sie LPUSH, um Jobs links hinzuzufügen, und BRPOP, um zu blockieren und von rechts zu poppen (FIFO).
// Producer: Job hinzufügen
await redis.lpush('email_queue', JSON.stringify({ to: 'user@example.com', subject: 'Willkommen' }));
// Consumer: Job verarbeiten (in einem Worker)
while (true) {
const job = await redis.brpop('email_queue', 0); // 0 = unbegrenzt blockieren
const { to, subject } = JSON.parse(job[1]);
await sendEmail(to, subject);
}
Zuverlässige Queues mit BRPOPLPUSH
Um den Verlust von Jobs zu vermeiden, wenn ein Worker abstürzt, verwenden Sie BRPOPLPUSH, um den Job atomar in eine Verarbeitungsliste zu verschieben. Nach erfolgreicher Verarbeitung entfernen Sie ihn aus der Verarbeitungsliste. Wenn der Worker stirbt, kann ein anderer Worker Jobs aus der Verarbeitungsliste wiederherstellen.
const job = await redis.brpoplpush('email_queue', 'processing_queue', 0);
try {
// Job verarbeiten
await redis.lrem('processing_queue', 1, job);
} catch (err) {
// Fehler behandeln, eventuell erneut in die Warteschlange stellen
}
Spezialisierte Queue-Bibliotheken
Für den Produktionseinsatz sollten Sie Bibliotheken wie Bull (Node.js), RQ (Python) oder Sidekiq (Ruby) in Betracht ziehen. Sie bieten Wiederholungsversuche, Planung und Monitoring out of the box.
3. Rate Limiting mit Redis
Rate Limiting schützt Ihre API vor Missbrauch und gewährleistet eine faire Nutzung. Die atomaren Operationen von Redis machen es ideal für die Zählung von Anfragen.
Fixed Window Rate Limiting
Erhöhen Sie einen Zähler für jeden Benutzer pro Zeitfenster. Wenn der Zähler das Limit überschreitet, lehnen Sie die Anfrage ab.
async function isAllowed(userId, limit = 100, windowSec = 60) {
const key = `rate:${userId}:${Math.floor(Date.now() / 1000 / windowSec)}`;
const count = await redis.incr(key);
if (count === 1) await redis.expire(key, windowSec);
return count