Redis ist ein In-Memory-Datenspeicher, der Anwendungen durch die Zwischenspeicherung teurer Datenbankabfragen und API-Antworten erheblich beschleunigt. Ein gut platzierter Cache kann eine 200 ms lange Datenbankabfrage in einen 2 ms langen Cache-Treffer verwandeln. In diesem Leitfaden wird Redis-Caching auf die richtige Art und Weise zu einer Node.js-App hinzugefügt.
📋 Table of Contents
Warum mit Redis zwischenspeichern?
- Geschwindigkeit: In-Memory-Lesevorgänge sind 10-100-mal schneller als Datenbankabfragen
- Datenbanklast reduzieren: Bedienen Sie wiederholte Anfragen aus dem Cache und schonen Sie Ihre Datenbank
- Maßstab: Bewältigen Sie Verkehrsspitzen, ohne Ihren primären Datenspeicher zu überlasten
- Vielseitig: Verwaltet auch Sitzungen, Ratenbegrenzung, Warteschlangen und Pub/Sub
Einrichtung
npm install ioredis
# Run Redis locally with Docker
docker run -d --name redis -p 6379:6379 redis:7-alpine
// redis.js — connection singleton
import Redis from 'ioredis';
export const redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
maxRetriesPerRequest: 3,
retryStrategy: (times) => Math.min(times * 50, 2000),
});
redis.on('error', (err) => console.error('Redis error:', err));
redis.on('connect', () => console.log('Redis connected'));
Das Cache-Aside-Muster (am häufigsten)
Überprüfen Sie zuerst den Cache. Bei einem Fehlschlag aus der Datenbank abrufen und den Cache füllen:
import { redis } from './redis';
async function getUser(userId) {
const cacheKey = `user:${userId}`;
// 1. Try cache first
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached); // cache hit — fast path
}
// 2. Cache miss — fetch from database
const user = await db.users.findById(userId);
if (!user) return null;
// 3. Populate cache with a TTL (expire after 1 hour)
await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600);
return user;
}
Wiederverwendbarer Cache-Wrapper
async function cached(key, ttlSeconds, fetchFn) {
const hit = await redis.get(key);
if (hit) return JSON.parse(hit);
const data = await fetchFn();
if (data !== null && data !== undefined) {
await redis.set(key, JSON.stringify(data), 'EX', ttlSeconds);
}
return data;
}
// Usage — clean and reusable
const products = await cached('products:featured', 600, () =>
db.products.findFeatured()
);
const user = await cached(`user:${id}`, 3600, () =>
db.users.findById(id)
);
Cache-Ungültigmachung (der schwierige Teil)
// When data changes, invalidate the cache to avoid stale reads
async function updateUser(userId, updates) {
const user = await db.users.update(userId, updates);
// Invalidate the cached version
await redis.del(`user:${userId}`);
// Invalidate related caches too
await redis.del('users:list');
return user;
}
// Invalidate by pattern (use SCAN, not KEYS, in production)
async function invalidatePattern(pattern) {
const stream = redis.scanStream({ match: pattern, count: 100 });
stream.on('data', (keys) => {
if (keys.length) redis.del(...keys);
});
}
// invalidatePattern('user:*'); // clear all user caches
Express-Middleware für Routen-Caching
function cacheRoute(ttlSeconds) {
return async (req, res, next) => {
const key = `route:${req.originalUrl}`;
const cached = await redis.get(key);
if (cached) {
res.setHeader('X-Cache', 'HIT');
return res.json(JSON.parse(cached));
}
// Override res.json to cache the response
const originalJson = res.json.bind(res);
res.json = (body) => {
redis.set(key, JSON.stringify(body), 'EX', ttlSeconds);
res.setHeader('X-Cache', 'MISS');
return originalJson(body);
};
next();
};
}
// Cache this route for 5 minutes
app.get('/api/products', cacheRoute(300), getProducts);
Ratenbegrenzung mit Redis
async function rateLimit(req, res, next) {
const key = `ratelimit:${req.ip}`;
const limit = 100; // requests
const window = 60; // seconds
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, window); // set TTL on first request
}
if (current > limit) {
return res.status(429).json({ error: 'Too many requests' });
}
res.setHeader('X-RateLimit-Remaining', Math.max(0, limit - current));
next();
}
Best Practices für das Caching
- Legen Sie immer eine TTL fest: Verhindert, dass veraltete Daten für immer existieren, und kontrolliert den Speicher
- Speichern Sie, was teuer ist und häufig gelesen wird: Nicht alles zwischenspeichern – die Hot-Pfade
- zwischenspeichern Behandeln Sie Cache-Fehler ordnungsgemäß: Wenn Redis ausgefallen ist, greifen Sie auf die Datenbank zu und stürzen Sie nicht ab
- Verwenden Sie eine einheitliche Schlüsselbenennung:
entity:id:fieldKonventionen machen die Invalidierung vorhersehbar - Vermeiden Sie den Befehl KEYS in der Produktion: Es blockiert Redis – verwenden Sie stattdessen SCAN
- Achten Sie auf Cache-Anstürme: Wenn ein beliebter Schlüssel abläuft, treffen viele Anfragen gleichzeitig auf die Datenbank – verwenden Sie Sperren oder gestaffelte TTLs
Häufig gestellte Fragen
F: Was sollte ich NICHT zwischenspeichern?
A: Hochflüchtige Daten, die sich bei jeder Anfrage ändern, sensible Daten ohne angemessene Sicherheit und Daten, deren Berechnung kostengünstig ist. Speichern Sie die teuren, häufig gelesenen und relativ stabilen Daten im Cache.
F: Wie wähle ich eine TTL aus?
A: Balance zwischen Frische und Leistung. Benutzerprofile: 1 Stunde. Produktlisten: 5–10 Minuten. Echtzeitdaten: Sekunden oder nicht zwischenspeichern. Passen Sie die TTL an die Häufigkeit an, mit der sich die Daten tatsächlich ändern.
F: Was passiert, wenn Redis ausfällt?
A: Ihre App sollte sich ordnungsgemäß verschlechtern – Redis-Fehler abfangen und in die Datenbank gelangen. Lassen Sie niemals zu, dass ein Cachefehler Ihre App lahmlegt. Cache-Aufrufe in Try/Catch einschließen.
F: Redis vs. In-Memory-Caching (wie eine JS-Map)?
A: In-Memory-Map-Caches gelten pro Prozess (verloren beim Neustart und werden nicht von allen Instanzen gemeinsam genutzt). Redis wird von allen Ihren App-Instanzen gemeinsam genutzt und bleibt bestehen – wichtig für horizontal skalierte Apps.
F: Wie verhindere ich Cache-Stampedes?
A: Verwenden Sie eine Sperre (SET NX), damit nur eine Anfrage den Cache aktualisiert, während andere warten oder veraltete Daten bereitstellen, oder fügen Sie zufälligen Jitter zu TTLs hinzu, damit beliebte Schlüssel nicht alle gleichzeitig ablaufen.
Fazit
Redis-Caching kann die Leistung Ihrer Node.js-App steigern und langsame Datenbankabfragen in sofortige Cache-Treffer verwandeln. Das Cache-Aside-Muster mit einem wiederverwendbaren Wrapper deckt die meisten Anforderungen ab, Middleware auf Routenebene speichert gesamte Antworten zwischen und dieselbe Redis-Instanz übernimmt die Ratenbegrenzung und Sitzungen. Denken Sie an die beiden schwierigen Teile: Legen Sie immer eine TTL fest und machen Sie den Cache ungültig, wenn sich Daten ändern. Behandeln Sie Redis-Fehler ordnungsgemäß und Ihre App wird gleichzeitig schneller und ausfallsicherer.
🔗 Share this article
✍️ Leave a Comment