Cas d'utilisation de Redis : Cache, Files d'attente et Limitation de débit
Vous avez construit une application web qui fonctionne bien avec quelques utilisateurs, mais à mesure que le trafic augmente, vous remarquez des chargements de page lents, des tâches en arrière-plan dupliquées et des API bombardées par des clients abusifs. Ce sont des problèmes de mise à l'échelle classiques que Redis peut résoudre élégamment. Redis est un magasin de données en mémoire qui excelle dans trois tâches principales : le cache, les files d'attente et la limitation de débit. Dans cet article, nous explorerons chaque cas d'utilisation avec des exemples pratiques et des bonnes pratiques.
Pourquoi Redis pour ces cas d'utilisation ?
Redis stocke les données en mémoire, ce qui le rend incroyablement rapide—souvent avec des temps de réponse inférieurs à la milliseconde. Il prend en charge des structures de données riches comme les chaînes, les listes, les ensembles, les ensembles triés et les hachages, qui correspondent naturellement au cache, aux files d'attente et à la limitation de débit. Il offre également des opérations atomiques, pub/sub et des options de persistance, ce qui en fait un outil polyvalent pour les développeurs backend.
1. Cache avec Redis
Le cache est le cas d'utilisation le plus courant de Redis. En stockant les données fréquemment consultées dans Redis, vous réduisez la charge de la base de données et accélérez les réponses.
Comment fonctionne le cache
Lorsqu'une requête arrive, vérifiez si les données existent dans Redis. Si oui (succès du cache), retournez-les directement. Sinon (échec du cache), récupérez-les depuis la base de données, stockez-les dans Redis avec un temps d'expiration, et retournez-les. Ce modèle est appelé cache-aside.
// Exemple Node.js utilisant 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); // TTL d'une heure
return user;
}
Bonnes pratiques pour le cache
- Définir un TTL : Définissez toujours un temps d'expiration pour éviter les données obsolètes et la surcharge de mémoire.
- Utiliser des structures de données appropriées : Pour un simple clé-valeur, utilisez des chaînes. Pour les objets, utilisez des hachages pour mettre à jour les champs individuellement.
- Gérer l'invalidation du cache : Mettez à jour ou supprimez les entrées de cache lorsque les données sous-jacentes changent.
- Surveiller le taux de succès : Un faible taux de succès signifie que votre cache n'est pas efficace ; ajustez les clés ou les TTL.
Stratégies d'invalidation du cache
L'invalidation est difficile. Les stratégies courantes incluent :
- Basée sur le temps : Comptez sur le TTL. Simple mais peut servir des données obsolètes.
- Write-through : Mettez à jour le cache chaque fois que vous mettez à jour la base de données.
- Write-behind : Mettez à jour le cache d'abord, puis mettez à jour la base de données de manière asynchrone (risque de perte de données).
2. Files d'attente avec Redis
Les files d'attente découplent les tâches chronophages du cycle requête-réponse. Par exemple, l'envoi d'e-mails, le traitement d'images ou la génération de rapports peuvent être effectués de manière asynchrone.
Implémentation d'une file d'attente simple
Les listes Redis sont parfaites pour les files d'attente. Utilisez LPUSH pour ajouter des tâches à gauche et BRPOP pour bloquer et retirer de la droite (FIFO).
// Producteur : ajouter une tâche
await redis.lpush('email_queue', JSON.stringify({ to: 'user@example.com', subject: 'Welcome' }));
// Consommateur : traiter la tâche (dans un worker)
while (true) {
const job = await redis.brpop('email_queue', 0); // 0 = bloquer indéfiniment
const { to, subject } = JSON.parse(job[1]);
await sendEmail(to, subject);
}
Files d'attente fiables avec BRPOPLPUSH
Pour éviter de perdre des tâches si un worker plante, utilisez BRPOPLPUSH pour déplacer atomiquement la tâche vers une liste de traitement. Après un traitement réussi, retirez-la de la liste de traitement. Si le worker meurt, un autre worker peut récupérer les tâches de la liste de traitement.
const job = await redis.brpoplpush('email_queue', 'processing_queue', 0);
try {
// traiter la tâche
await redis.lrem('processing_queue', 1, job);
} catch (err) {
// gérer l'erreur, peut-être remettre en file d'attente
}
Bibliothèques de files d'attente dédiées
Pour la production, envisagez des bibliothèques comme Bull (Node.js), RQ (Python) ou Sidekiq (Ruby). Elles fournissent des tentatives, une planification et une surveillance prêtes à l'emploi.
3. Limitation de débit avec Redis
La limitation de débit protège votre API contre les abus et assure une utilisation équitable. Les opérations atomiques de Redis le rendent idéal pour compter les requêtes.
Limitation de débit à fenêtre fixe
Incrémentez un compteur pour chaque utilisateur par fenêtre de temps. Si le compteur dépasse la limite, rejetez la requête.
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