B
ByteByteGo
#Redis#System Design#Caching

Redis pour la Conception de Systèmes : Guide Complet et Cas d'Usage

Découvrez Redis, un serveur de structures de données en mémoire, rapide et polyvalent, essentiel pour la conception de systèmes. Apprenez son fonctionnement, sa persistance, son scaling et ses applications concrètes comme le caching et les classements.

5 min de lectureGuide IA

Introduction

Introduction
Redis est un serveur de structures de données en mémoire, rapide et polyvalent, qui offre une latence très faible et un comportement prévisible. Il est couramment utilisé dans les projets réels pour des applications telles que les limiteurs de débit, les applications de covoiturage et les applications de chat, agissant comme un cache, un magasin de données éphémères ou un composant pour des fonctionnalités spécifiques.

Précis de configuration

Élément Version / Lien
Langage / Runtime C (implémentation principale)
Librairie principale Redis (serveur)
APIs requises Commandes Redis (ex: SET, GET, INCR, ZADD)
Clés / credentials nécessaires Aucune mention spécifique dans la vidéo, dépend de la configuration de sécurité (ex: requirepass dans redis.conf)

Guide étape par étape

Étape 1 — Exécution des commandes

Étape 1 — Exécution des commandes
Redis est un serveur de structures de données en mémoire, mono-threadé. Cela signifie qu'il traite les commandes une par une, garantissant un comportement prévisible et évitant les problèmes de concurrence comme les verrous ou les écritures simultanées sur la même clé. Pour maintenir des performances élevées malgré le mono-threading, Redis utilise le pipelining et les transactions pour regrouper plusieurs commandes en un seul aller-retour réseau.

Exemple de commandes simples :

SET name "ByteByteGo"  // Définit la clé 'name' avec la valeur 'ByteByteGo'
GET name              // Récupère la valeur de la clé 'name'

LPUSH name GFG city:Arlington // Ajoute un élément à gauche d'une liste nommée 'name'
LPUSH name GFG articles:500   // Ajoute un autre élément à gauche de la liste 'name'
RPOP name:GFG                 // Retire et retourne l'élément le plus à droite de la liste 'name'

HMSET user:1 name GFG city Arlington State TX Cost 200 // Définit plusieurs champs dans un hachage pour la clé 'user:1'

SET counter 5   // Initialise un compteur à 5
GET counter     // Récupère la valeur du compteur (retourne 5)
INCR counter    // Incrémente atomiquement le compteur (retourne 6)

Pipelining et Transactions :
Pour réduire la latence réseau, les clients peuvent envoyer plusieurs commandes en une seule fois (pipelining) ou les regrouper dans une transaction. Le serveur Redis exécute toujours les commandes séquentiellement, mais le socket reste occupé, améliorant le débit.

Pipelining :

GET profile:42
INCR counter
SET user:1:name "Alex"

Ces trois commandes sont envoyées au serveur en un seul bloc, et les réponses sont reçues ensemble.

Transaction :

MULTI             // Démarre une transaction
GET profile:42    // Met la commande en file d'attente
INCR counter      // Met la commande en file d'attente
SET user:1:name "Alex" // Met la commande en file d'attente
EXEC              // Exécute toutes les commandes en file d'attente atomiquement

Étape 2 — Stockage et Persistance

Étape 2 — Stockage et Persistance
Étant donné que Redis stocke les données en mémoire vive (RAM), il offre une latence très faible (sous-milliseconde). Cependant, cela pose un défi pour la durabilité, car les données en RAM sont volatiles. Différentes stratégies de persistance sont utilisées en fonction des besoins de l'application.

1. Redis comme cache pur (sans persistance) :
Dans ce scénario, Redis est utilisé uniquement comme un cache. La base de données principale est la source de vérité. Les écritures vont à la base de données, et Redis ne stocke que les résultats mis en cache. Si Redis tombe en panne, le cache est perdu mais peut être reconstruit à la demande en interrogeant la base de données. Aucune donnée critique n'est perdue.

2. Réplication :
Pour améliorer la disponibilité et le débit de lecture, des répliques peuvent être ajoutées. Le nœud primaire gère toutes les écritures, tandis que les répliques gèrent le trafic de lecture. Si le primaire tombe en panne, une réplique est promue pour le remplacer. Cela peut entraîner une brève indisponibilité pendant le basculement, mais la plupart des données en cache sont préservées sur les répliques. Le compromis est une surcharge de mémoire, car chaque réplique double l'empreinte mémoire.

3. Instantanés RDB (Redis Database) :
Redis peut être configuré pour prendre des instantanés périodiques de l'état de la mémoire et les enregistrer sur disque dans un fichier .rdb. Lors d'un redémarrage, Redis charge cet instantané en mémoire, permettant au cache de démarrer avec des données "chaudes" plutôt qu'un cache vide. Ce compromis accepte la perte des écritures effectuées entre le dernier instantané et la panne, ce qui est souvent acceptable pour les charges de travail de cache.

4. Fichier AOF (Append-Only File) :
Pour les données critiques qui ne peuvent pas être perdues, le fichier AOF peut être activé. Redis ajoute chaque écriture à un fichier de log sur disque. Une configuration courante est appendfsync everysec, où le système d'exploitation vide les écritures en mémoire tampon sur le disque environ une fois par seconde. En cas de panne, on perd au maximum une seconde de données. Pour une durabilité plus forte, appendfsync always peut être configuré, mais cela ralentit considérablement les opérations et a un impact majeur sur le débit, il est donc généralement évité sauf pour de petits ensembles de données où la latence n'est pas critique.

Étape 3 — Mise à l'échelle de Redis

La plupart des équipes commencent avec une seule instance Redis. Sur un matériel décent, une seule instance peut gérer un grand nombre d'opérations par seconde pour de nombreuses charges de travail.

1. Mise à l'échelle des lectures avec des répliques :
Lorsque les lectures deviennent un goulot d'étranglement, l'ajout de répliques est la première étape. Le nœud primaire accepte toutes les écritures, et les répliques servent le trafic de lecture. Cela augmente le débit de lecture, mais le débit d'écriture reste limité par le primaire.

2. Mise à l'échelle des écritures avec le sharding côté client :
Lorsque le volume d'écriture devient trop important pour une seule instance, de nombreuses équipes adoptent le sharding côté client. Cela implique d'exécuter plusieurs instances Redis indépendantes (shards). L'application cliente hache chaque clé (par exemple, id % N) et sélectionne un nœud Redis basé sur ce hachage. Pour un cluster statique, un simple hachage modulo fonctionne. Pour une mise à l'échelle dynamique, des bibliothèques comme Katama utilisent le hachage cohérent, de sorte que l'ajout ou la suppression d'un nœud ne redistribue pas toutes les clés. Chaque nœud est indépendant, sans coordination entre les nœuds ni protocole distribué entre les serveurs Redis. Si un nœud tombe en panne, cela entraîne simplement des échecs de cache pour le sous-ensemble de clés qui résident sur ce nœud, jusqu'à ce que le cache se repeuple.

3. Cluster Redis :
Le Cluster Redis est une option qui fournit un sharding et un basculement automatiques. Cependant, il introduit une complexité supplémentaire en termes d'exploitation et de débogage. Pour cette raison, de nombreuses équipes préfèrent le sharding côté client pour les charges de travail de cache et n'adoptent le Cluster Redis que lorsqu'elles ont besoin de ses garanties spécifiques.

Étape 4 — Cas d'usage courants de Redis

1. Caching :
Le cas d'utilisation classique de Redis est le caching. Un service vérifie d'abord Redis avant d'interroger la base de données. En cas de succès (cache hit), la valeur est retournée immédiatement. En cas d'échec (cache miss), la base de données est interrogée, le résultat est stocké dans Redis (souvent avec une durée de vie TTL) et la réponse est renvoyée au client. Pour gérer la taille du cache, on peut définir un TTL sur chaque clé ou configurer une politique d'éviction (par exemple, LRU - Least Recently Used) lorsque la mémoire est pleine.

2. Limiteur de débit (Rate Limiter) :
Plusieurs instances de service peuvent partager des compteurs via Redis pour implémenter un limiteur de débit distribué. Des commandes d'incrémentation atomiques (INCR) sont utilisées sur des clés représentant un utilisateur, une adresse IP ou un jeton API. Combinées avec des TTL ou des scripts Lua, cela permet d'implémenter divers algorithmes de limitation de débit (par exemple, le leaky bucket algorithm) sans avoir besoin d'un service de coordination séparé.

3. Classements (Leaderboards) :
Les Sorted Sets (zset) de Redis sont parfaits pour les classements. Un Sorted Set maintient les éléments ordonnés par un score numérique. On peut insérer un joueur avec un score, mettre à jour son score, interroger les N meilleurs joueurs ou rechercher le rang d'un joueur. Ces opérations s'exécutent généralement en temps logarithmique par rapport à la taille de l'ensemble. Ce modèle se généralise bien pour construire des listes de publications tendances, des meilleurs vendeurs, des utilisateurs les plus actifs, et de nombreux autres problèmes de classement.

Tableaux comparatifs

Client-side Sharding vs. Redis Cluster

Critère Client-side Sharding Redis Cluster
Sharding Manuel, géré par le client Automatique, géré par le cluster
Basculement Géré par le client ou un système externe Automatique, géré par le cluster
Complexité opérationnelle Plus simple Plus complexe
Coordination inter-nœuds Aucune Oui, protocole distribué
Cas d'usage typique Cache, données éphémères Données critiques nécessitant des garanties fortes

Politiques de persistance AOF

Critère appendfsync everysec appendfsync always
Vitesse Rapide Très lent
Perte de données Max 1 seconde Aucune (plus forte durabilité)
Impact sur le débit Faible Élevé
Cas d'usage Durabilité raisonnable pour la plupart des applications Données extrêmement critiques, petits ensembles de données, latence non critique

⚠️ Erreurs fréquentes et pièges

  1. Négliger la persistance pour les données critiques : Utiliser Redis sans persistance (ou avec des instantanés RDB trop espacés) pour des données qui ne peuvent pas être perdues. Solution : Pour les données critiques, configurer l'AOF avec appendfsync everysec ou always selon les exigences de durabilité, ou utiliser Redis comme cache devant une base de données durable.
  2. Bloquer le thread unique de Redis : Exécuter des commandes longues ou des scripts Lua complexes qui bloquent le thread principal de Redis, entraînant une latence élevée pour toutes les autres requêtes. Solution : Garder les opérations Redis courtes et atomiques. Utiliser des scripts Lua avec parcimonie et s'assurer qu'ils s'exécutent rapidement. Pour les tâches de fond, décharger le traitement vers des workers séparés.
  3. Ignorer la surcharge mémoire des répliques : Ajouter de nombreuses répliques sans tenir compte de l'augmentation de l'empreinte mémoire totale, ce qui peut entraîner des coûts élevés ou des problèmes de performance si la RAM est insuffisante. Solution : Planifier la capacité mémoire en fonction du nombre de répliques et de la taille des données. Optimiser l'utilisation de la mémoire dans Redis (par exemple, en utilisant des structures de données plus efficaces).
  4. Mauvaise gestion du sharding côté client : Utiliser un sharding côté client sans mécanisme de re-hachage ou de migration des données lors de l'ajout/suppression de nœuds, ce qui peut entraîner des clés inaccessibles ou une distribution inégale. Solution : Utiliser des bibliothèques de hachage cohérent (comme Katama) ou des solutions de sharding qui gèrent la redistribution des données de manière transparente lors des changements de topologie.

Glossaire

Mono-threadé : Un processus qui exécute une seule séquence d'instructions à la fois, garantissant l'atomicité des opérations sur les données partagées.
Pipelining : Une technique qui permet à un client d'envoyer plusieurs commandes à un serveur Redis sans attendre la réponse de chaque commande, réduisant ainsi la latence due aux allers-retours réseau.
Sorted Set (ZSET) : Une structure de données Redis qui stocke des membres uniques associés à un score numérique, permettant de récupérer les membres par score ou par rang.

Points clés à retenir

  • Redis est un serveur de structures de données en mémoire, mono-threadé, offrant une latence très faible et un comportement prévisible.
  • Le pipelining et les transactions sont essentiels pour optimiser le débit en regroupant les commandes et en réduisant les allers-retours réseau.
  • La persistance des données dans Redis peut être configurée via des instantanés RDB (pour les caches) ou le fichier AOF (pour les données critiques), avec des compromis entre durabilité et performance.
  • La mise à l'échelle de Redis commence par une instance unique, puis l'ajout de répliques pour les lectures, et enfin le sharding (côté client ou via Redis Cluster) pour les écritures.
  • Les structures de données natives de Redis (Strings, Lists, Hashes, Sets, Sorted Sets, Streams) simplifient l'implémentation de fonctionnalités complexes comme les limiteurs de débit et les classements.
  • Pour les données non critiques, Redis est souvent utilisé comme un cache sans persistance, la base de données principale étant la source de vérité.
  • Le choix entre le sharding côté client et le Cluster Redis dépend de la complexité opérationnelle acceptable et des garanties de basculement automatique requises.

Ressources