Comment utiliser les listes Redis (LPUSH, RPOP) comme files d'attente de messages

Apprenez à transformer les listes Redis en un puissant système de file d'attente de messages. Ce tutoriel couvre les commandes essentielles LPUSH et RPOP, montrant comment mettre des tâches en file d'attente et permettre aux travailleurs de les retirer et de les traiter de manière fiable. Explorez des exemples pratiques en Python et découvrez les considérations clés pour construire des files d'attente de messages robustes basées sur FIFO avec Redis pour le traitement asynchrone des tâches.

Comment utiliser les listes Redis (LPUSH, RPOP) comme files d'attente de messages

Les listes Redis peuvent constituer une file d'attente de messages légère utile lorsque vous avez besoin de quelque chose de plus simple que RabbitMQ, Kafka ou un framework complet de tâches en arrière-plan. Le schéma habituel est simple : les producteurs ajoutent des tâches avec LPUSH, et les travailleurs prennent les tâches avec RPOP ou BRPOP.

Cette simplicité est la raison pour laquelle les gens l'utilisent. Une requête web peut déposer une tâche d'e-mail dans Redis et revenir rapidement. Un travailleur peut récupérer cette tâche un instant plus tard. Vous n'avez pas besoin d'une topologie de courtier, d'échanges, de sujets ou d'une nouvelle pile opérationnelle. Vous devez cependant être honnête sur le compromis : un RPOP simple supprime le message avant que le travailleur n'ait terminé le travail. Si le travailleur plante au mauvais moment, cette tâche est perdue à moins que vous ne construisiez un modèle d'accusé de réception autour de cela.

Comprendre les listes Redis comme files d'attente

Une liste Redis est une collection ordonnée de chaînes de caractères. Elle peut être vue comme une séquence d'éléments, et Redis fournit des commandes pour ajouter ou supprimer des éléments de la tête ou de la queue de la liste. Cette nature double fait que les listes sont intrinsèquement adaptées aux implémentations de files d'attente.

  • Mise en file d'attente (Ajout de messages) : Nous pouvons ajouter de nouveaux messages à la file d'attente en les poussant sur une extrémité de la liste. La commande LPUSH pousse les éléments vers la tête (côté gauche) d'une liste.
  • Retrait de la file d'attente (Traitement des messages) : Nous pouvons récupérer et supprimer les messages de la file d'attente en les extrayant de l'autre extrémité de la liste. La commande RPOP extrait les éléments de la queue (côté droit) d'une liste.

Cette combinaison spécifique (LPUSH pour la mise en file d'attente et RPOP pour le retrait) crée une file d'attente Premier entré, premier sorti (FIFO), qui est le comportement le plus courant et attendu pour une file d'attente de messages.

Commandes principales : LPUSH et RPOP

Examinons les deux commandes principales qui constituent l'épine dorsale de notre file d'attente de messages Redis.

LPUSH key value [value ...]

La commande LPUSH insère une ou plusieurs valeurs de chaîne à la tête (côté gauche) de la liste stockée à key. Si la key n'existe pas, une nouvelle liste est créée et les valeurs sont insérées.

Exemple :

Imaginez que vous ayez une tâche à traiter, comme l'envoi d'un e-mail. Vous pouvez pousser cette tâche comme un message sur une liste Redis nommée email_tasks.

# Pousser une seule tâche d'e-mail
LPUSH email_tasks "{'to': '[email protected]', 'subject': 'Welcome!', 'body': 'Thanks for signing up!'}"

# Pousser une autre tâche, elle sera placée avant la précédente
LPUSH email_tasks "{'to': '[email protected]', 'subject': 'New User Registration', 'body': 'A new user has registered.'}"

Après ces commandes, la liste email_tasks ressemblera à ceci (de la tête à la queue) :

1) "{'to': '[email protected]', 'subject': 'New User Registration', 'body': 'A new user has registered.'}"
2) "{'to': '[email protected]', 'subject': 'Welcome!', 'body': 'Thanks for signing up!'}"

RPOP key

La commande RPOP supprime et renvoie le dernier élément (de la queue, côté droit) de la liste stockée à key. Si la liste est vide, elle renvoie nil.

Exemple :

Un processus de travailleur peut interroger périodiquement la liste email_tasks pour de nouvelles tâches en utilisant RPOP.

# Un travailleur tente de récupérer une tâche
RPOP email_tasks

Si la liste n'est pas vide, RPOP renverra le dernier élément poussé (qui est le premier élément de la queue). Dans notre exemple ci-dessus, le premier appel à RPOP renverrait :

"{'to': '[email protected]', 'subject': 'Welcome!', 'body': 'Thanks for signing up!'}"

Les appels suivants récupéreraient ensuite la prochaine tâche disponible depuis la queue.

Construire un système de file d'attente de messages de base

Décrivons le flux typique d'une file d'attente de messages simple utilisant LPUSH et RPOP.

1. Producteur (Mise en file d'attente des tâches)

Toute partie de votre application qui doit déléguer du travail peut agir comme un producteur. Il construit un message (souvent une chaîne JSON représentant les détails de la tâche) et le pousse sur une liste Redis en utilisant LPUSH.

Logique du producteur (Exemple conceptuel en Python) :

import redis
import json

r = redis.Redis(host='localhost', port=6379, db=0)

def send_email_task(to_email, subject, body):
    task_message = {
        'type': 'send_email',
        'payload': {
            'to': to_email,
            'subject': subject,
            'body': body
        }
    }
    # LPUSH ajoute à la tête de la liste 'email_queue'
    r.lpush('email_queue', json.dumps(task_message))
    print(f"Tâche d'e-mail poussée dans la file d'attente : {to_email}")

# Exemple d'utilisation :
send_email_task('[email protected]', 'Bonjour du producteur', 'Ceci est un message de test.')
send_email_task('[email protected]', 'Mise à jour importante', 'Nouvelles fonctionnalités disponibles.')

2. Consommateur (Retrait et traitement des tâches)

Les processus de travailleurs, s'exécutant indépendamment, surveilleront en continu la liste Redis pour de nouveaux messages. Ils utilisent RPOP pour récupérer et supprimer un message de la file d'attente.

Logique du consommateur (Exemple conceptuel en Python) :

import redis
import json
import time

r = redis.Redis(host='localhost', port=6379, db=0)

def process_tasks():
    while True:
        # RPOP tente d'obtenir un message de la queue de la liste 'email_queue'
        message_bytes = r.rpop('email_queue')
        if message_bytes:
            message_str = message_bytes.decode('utf-8')
            try:
                task = json.loads(message_str)
                print(f"Traitement de la tâche : {task}")
                # Simuler le traitement de la tâche
                if task.get('type') == 'send_email':
                    print(f"  -> Envoi d'un e-mail à {task['payload']['to']}...")
                    # Remplacer par la logique réelle d'envoi d'e-mail
                    time.sleep(1) # Simuler le travail
                    print(f"  -> E-mail envoyé à {task['payload']['to']}.")
                else:
                    print(f"  -> Type de tâche inconnu : {task.get('type')}")
            except json.JSONDecodeError:
                print(f"Erreur de décodage JSON : {message_str}")
            except Exception as e:
                print(f"Erreur lors du traitement de la tâche {message_str} : {e}")
        else:
            # Pas de message, attendre un peu avant de réinterroger
            # print("Aucune tâche disponible, attente...")
            time.sleep(0.5)

if __name__ == "__main__":
    print("Travailleur démarré. En attente de tâches...")
    process_tasks()

Lorsque vous exécutez le producteur, il pousse les messages. Lorsque vous exécutez le consommateur, il commencera à les récupérer et à les traiter. L'ordre de traitement correspondra à l'ordre dans lequel ils ont été poussés (FIFO) car LPUSH ajoute à la tête et RPOP supprime de la queue.

Considérations sur la fiabilité

Bien que LPUSH et RPOP fournissent un mécanisme de file d'attente de base, construire une file d'attente de production signifie décider ce qui doit se passer lorsque les travailleurs plantent, les tâches échouent ou les producteurs envoient des charges utiles malformées.

1. Perte de message pendant le traitement

Si un processus de travailleur plante après que RPOP a supprimé le message mais avant d'avoir terminé le traitement, ce message est perdu. Pour éviter cela :

  • Utilisez BRPOP au lieu d'une interrogation serrée : BRPOP bloque jusqu'à ce qu'une liste ait un élément ou jusqu'à ce qu'un délai d'attente soit atteint. Cela ne rend pas le traitement fiable en soi, mais cela empêche les travailleurs de se réveiller toutes les quelques millisecondes juste pour trouver une file d'attente vide.
    # Extraction bloquante depuis la droite, avec un délai d'attente de 0 (bloquer indéfiniment)
    BRPOP email_queue 0
    
  • Utilisez une liste de traitement pour l'accusé de réception : Un modèle courant consiste à déplacer atomiquement un message de email_queue vers email_processing, le traiter, puis le supprimer de email_processing uniquement après la réussite du travail. Si un travailleur meurt, un processus de récupération séparé peut rechercher les éléments obsolètes dans la liste de traitement et les remettre dans la file d'attente principale. RPOPLPUSH est la commande classique pour ce modèle, et les versions plus récentes de Redis fournissent également LMOVE/BLMOVE.

2. Gestion des tâches échouées

Que se passe-t-il si une tâche échoue pendant le traitement (par exemple, en raison d'un problème réseau temporaire ou de mauvaises données) ?

  • Mécanismes de nouvelle tentative : Implémentez une logique de nouvelle tentative dans le travailleur. Après quelques échecs, déplacez la tâche vers une liste 'failed_tasks' pour inspection manuelle.
  • File d'attente de lettres mortes (DLQ) : Une liste Redis dédiée (ou autre stockage) où les messages qui échouent de manière répétée sont envoyés. Ceci est crucial pour le débogage et la récupération.

3. Plusieurs consommateurs

Si vous avez plusieurs instances de travailleurs consommant la même file d'attente, RPOP (et BRPOP) garantit que chaque message est traité par un seul travailleur. En effet, RPOP supprime atomiquement l'élément.

4. Ordre des messages

Bien que LPUSH et RPOP créent une file d'attente FIFO, cette garantie n'est aussi forte que votre logique de traitement. Si les consommateurs remettent en file d'attente les messages échoués sans gestion appropriée, ou si vous introduisez d'autres opérations, l'ordre FIFO strict pourrait être compromis.

5. Format de la charge utile et idempotence

Traitez le corps du message comme un petit contrat. JSON est courant car il est facile à inspecter dans redis-cli, mais utilisez du JSON valide plutôt que des dictionnaires à guillemets simples de style Python :

{"type":"send_email","id":"email-1842","payload":{"to":"[email protected]","template":"welcome"}}

Le champ id est important. Si un travailleur réessaie après un délai d'attente, ou si une tâche obsolète est remise en file d'attente à partir d'une liste de traitement, la même tâche logique peut s'exécuter plus d'une fois. Concevez le gestionnaire pour qu'un doublon soit inoffensif. Pour un travailleur d'e-mail, cela pourrait signifier enregistrer email-1842 dans la base de données de l'application avant d'envoyer, puis vérifier cet enregistrement avant que toute nouvelle tentative n'envoie un autre message.

6. Longueur de la file d'attente et contre-pression

Surveillez la longueur de la file d'attente avec LLEN email_queue. Une file d'attente qui s'allonge n'est pas automatiquement mauvaise ; cela peut simplement signifier que les travailleurs rattrapent leur retard après un pic de trafic. Une file d'attente qui s'allonge pendant des heures signifie généralement que les producteurs sont plus rapides que les consommateurs, que les travailleurs échouent ou qu'une dépendance lente retient tout le monde.

En pratique, j'aime alerter à la fois sur l'âge et la longueur. Les listes Redis ne stockent pas l'heure de mise en file d'attente séparément, alors mettez un horodatage dans la charge utile si l'âge de la tâche est important :

{"type":"resize_image","id":"img-991","created_at":"2026-05-24T08:15:00Z","payload":{"image_id":991}}

Ensuite, les journaux de votre travailleur peuvent vous dire si les tâches sont traitées avec des secondes de retard ou des heures de retard. C'est beaucoup plus utile que la longueur seule lorsque vous déboguez un incident réel.

Techniques avancées (brièvement)

  • RPOPLPUSH : Extrait atomiquement un message d'une liste et le pousse sur une autre (par exemple, une liste de 'traitement'). C'est une commande clé pour implémenter un traitement fiable avec accusés de réception.
  • BLPOP / BRPOP avec plusieurs clés : Bloque et extrait de la première liste qui devient non vide. Utile pour consommer à partir de plusieurs files d'attente.
  • Scripts Lua : Pour les opérations atomiques complexes que RPOPLPUSH ne couvre pas, les scripts Lua peuvent être utilisés pour garantir que les séquences critiques de commandes s'exécutent sans interruption.

Une forme de travailleur plus fiable

Pour tout ce qui est plus important qu'une notification au mieux, évitez le travailleur "extraire et espérer". Une forme plus sûre ressemble à ceci :

RPOPLPUSH email_queue email_processing

Le travailleur reçoit le message et Redis l'a déjà déplacé vers email_processing. Après l'envoi de l'e-mail et l'enregistrement du succès par l'application, le travailleur supprime cette charge utile exacte de la liste de traitement :

LREM email_processing 1 '{"type":"send_email","id":"email-1842"}'

Ce n'est toujours pas une file d'attente d'entreprise parfaite. LREM doit correspondre à la charge utile, les grandes listes de traitement peuvent devenir gênantes, et vous avez besoin d'un processus de récupération qui sait quand un message est assez vieux pour être réessayé. Mais cela change le mode d'échec de manière utile. Un plantage du travailleur ne supprime plus la seule copie de la tâche.

Si vous utilisez cette approche, mettez les métadonnées de nouvelle tentative dans le message ou stockez-les à côté de l'ID du message dans une autre clé. Par exemple, un récupérateur peut déplacer un message obsolète vers email_queue les premières fois, puis le déplacer vers email_failed après la limite de nouvelles tentatives. Cela vous donne un endroit pour inspecter les messages empoisonnés au lieu de regarder la même mauvaise charge utile échouer indéfiniment.

Quand les listes Redis sont la mauvaise file d'attente

Les listes Redis sont faciles à comprendre, mais elles ne sont pas toujours le bon outil. Si vous avez besoin de tâches différées, de priorités de tâches, de nouvelles tentatives planifiées, de visibilité du flux de travail ou d'historique d'audit à long terme, une bibliothèque de tâches ou un courtier dédié peut être moins de travail au final. Les flux Redis méritent également d'être envisagés car ils ont des groupes de consommateurs et une sémantique d'accusé de réception intégrés au type de données.

J'aime toujours les listes pour les petites files d'attente internes : génération de vignettes, réchauffement de cache, diffusion de webhooks où le système source peut réessayer, ou des tâches d'arrière-plan simples appartenant à une seule application. Dès que plusieurs équipes dépendent du contrat de la file d'attente, notez les attentes de livraison. "Au moins une fois", "Au plus une fois" et "Au mieux" ne sont pas des termes académiques lors d'un incident. Ils décident si les doublons sont acceptables, si les messages perdus sont tolérables et de quelle machinerie de récupération vous avez besoin.

Les listes Redis sont un bon choix lorsque vous avez besoin d'une file d'attente petite et compréhensible et que vous utilisez déjà Redis. Commencez avec LPUSH et BRPOP pour un travail d'arrière-plan simple. Ajoutez une liste de traitement, un compteur de nouvelles tentatives et une liste de lettres mortes une fois que la perte d'une tâche aurait de l'importance. Si vous avez besoin de planification différée, de priorités, de diffusion, de rétention longue ou de fortes garanties de livraison entre de nombreux services, c'est généralement le point où une file d'attente spécialisée devient plus facile à vivre qu'une pile croissante de conventions Redis.