Résolution des problèmes courants de configuration Redis Pub/Sub

Assurez une messagerie en temps réel fiable en maîtrisant les défis de configuration de Redis Pub/Sub. Ce guide fournit des étapes concrètes pour résoudre les problèmes de consommateurs lents, la principale cause d'instabilité, en utilisant la directive cruciale `client-output-buffer-limit`. Apprenez à diagnostiquer les pics de mémoire à l'aide de la commande `CLIENT LIST`, à gérer les connexions d'abonnés dédiées et à mettre en œuvre les meilleures pratiques pour l'isolation Pub/Sub à fort volume afin de maintenir l'intégrité du système.

Résolution des problèmes courants de configuration Redis Pub/Sub

Redis Pub/Sub est si simple que les équipes le traitent souvent comme un petit courtier de messages qu'elles n'ont pas besoin d'exploiter. Un éditeur appelle PUBLISH, les abonnés reçoivent les messages, et tout semble instantané.

Les problèmes commencent lorsque de vrais réseaux, des clients lents, des reconnexions et des instances Redis partagées sont impliqués. Pub/Sub n'a pas d'historique des messages, pas d'accusés de réception et pas de file d'attente de nouvelle tentative. C'est un mécanisme de diffusion en direct. Si un abonné est parti, le message est perdu pour cet abonné. Si un abonné est connecté mais ne peut pas lire assez vite, Redis doit conserver la sortie en attente pour ce client jusqu'à ce qu'une limite configurée le déconnecte.

Cela rend Redis Pub/Sub idéal pour l'invalidation de cache, les mises à jour de présence, les tableaux de bord en direct et les notifications de « dernière valeur ». C'est un mauvais choix pour les flux de travail où chaque message doit être traité exactement une fois ou rejoué après une panne. Pour cela, regardez Redis Streams, RabbitMQ, Kafka ou un autre courtier avec suivi de livraison.

Symptôme : la mémoire augmente alors que le trafic Pub/Sub est élevé

L'échec le plus courant de Pub/Sub est un abonné lent. L'éditeur va bien. Redis va bien au début. Un consommateur est en retard parce que son processus est en pause, son réseau est lent ou il effectue un travail coûteux à l'intérieur du gestionnaire de messages. Redis continue d'écrire des messages dans le tampon de sortie de ce client. Si le tampon peut croître sans limite utile, la pression mémoire se propage d'un mauvais abonné à l'ensemble de l'instance.

Vérifiez les clients :

redis-cli CLIENT LIST

Recherchez les clients avec des commandes Pub/Sub et une mémoire de sortie importante :

id=88 addr=10.0.4.12:51244 flags=P db=0 sub=3 psub=0 omem=13421772 obl=128 oll=64 cmd=subscribe

Champs utiles :

  • flags=P indique un client Pub/Sub.
  • sub et psub affichent le nombre d'abonnements aux canaux et aux motifs.
  • omem est la mémoire du tampon de sortie en octets.
  • obl et oll montrent les détails du backlog du tampon de sortie.
  • cmd=subscribe ou cmd=psubscribe confirme ce que fait le client.

Ne paniquez pas à propos de idle pour les abonnés. Une connexion Pub/Sub peut rester inactive du point de vue de la commande tout en recevant des messages poussés.

Corrigez les consommateurs lents avec les limites du tampon de sortie

Redis donne aux clients Pub/Sub leur propre classe de limite de tampon de sortie :

client-output-buffer-limit pubsub 32mb 8mb 60

Interprétez cela comme :

  • Déconnectez immédiatement si le tampon de sortie d'un client Pub/Sub atteint 32 Mo.
  • Déconnectez s'il reste au-dessus de 8 Mo pendant 60 secondes.

Les chiffres exacts doivent correspondre à votre trafic et au comportement de vos clients. L'erreur courante est de définir la limite à 0 0 0 parce que les déconnexions sont gênantes pendant les tests. Cela supprime la barrière de sécurité. En production, une déconnexion est généralement préférable à laisser un abonné bloqué consommer de la mémoire jusqu'à ce que Redis devienne instable.

Après avoir modifié redis.conf, rechargez ou redémarrez selon votre processus de déploiement. Vous pouvez également inspecter la valeur en direct :

redis-cli CONFIG GET client-output-buffer-limit

Si vous utilisez un service Redis géré, ce paramètre peut être exposé via le groupe de paramètres ou l'interface de configuration de votre fournisseur plutôt que par édition directe de fichier.

Symptôme : les abonnés se connectent mais ne reçoivent rien

Commencez par le test le plus simple possible. Ouvrez un terminal :

redis-cli SUBSCRIBE test.channel

Ouvrez-en un autre :

redis-cli PUBLISH test.channel hello

Si cela fonctionne depuis l'hôte Redis mais pas depuis votre application, vous avez probablement un problème côté client ou réseau. Si cela échoue partout, vérifiez l'authentification, les ACL, le routage du cluster et le nom réel du canal.

Les noms de canaux sont des chaînes d'octets exactes. events.user, events:User et events:user sont des canaux différents. Dans les grands systèmes, je préfère les constantes ou un petit module de nommage de canaux plutôt que des chaînes tapées à la main dispersées dans les services.

Les abonnements à des motifs ajoutent une autre source de confusion :

PSUBSCRIBE events:*

Cela correspond à events:user.created, mais pas à prod:events:user.created. Si votre application préfixe les noms d'environnement, incluez le préfixe dans le motif.

Problèmes d'authentification et d'ACL

Les déploiements Redis modernes utilisent souvent des utilisateurs ACL au lieu d'un seul mot de passe requirepass partagé. Un client Pub/Sub a besoin d'une autorisation pour se connecter, s'authentifier, s'abonner au motif de canal et parfois publier.

Une vérification rapide en CLI :

redis-cli -u redis://app-user:[email protected]:6379 PING
redis-cli -u redis://app-user:[email protected]:6379 SUBSCRIBE events:test

Symptômes d'authentification courants :

  • NOAUTH Authentication required : le client a envoyé une commande avant de s'authentifier.
  • WRONGPASS invalid username-password pair : mauvais mot de passe, mauvais utilisateur ou un secret tourné non déployé partout.
  • NOPERM this user has no permissions : l'utilisateur s'est authentifié mais manque d'autorisations de commande ou de canal.

Avec les ACL, les autorisations de canal sont distinctes des autorisations de clé. Un utilisateur peut être autorisé à exécuter des commandes de clé normales mais pas à s'abonner aux canaux que vous attendez. Examinez l'utilisateur configuré :

redis-cli ACL GETUSER app-user

Ne collez pas de secrets de production dans l'historique du shell pendant les tests. Utilisez des variables d'environnement ou un identifiant temporaire lorsque c'est possible.

Ne partagez pas les connexions Pub/Sub avec les commandes normales

Une fois qu'une connexion entre en mode abonné, ce n'est plus une connexion de commande Redis normale. Elle reçoit des messages poussés et ne peut émettre que des commandes liées à l'abonnement telles que SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PING, RESET ou des commandes de type quit selon le client et la version de Redis.

Si vous empruntez une connexion à un pool général et appelez SUBSCRIBE, cette connexion du pool n'est effectivement plus disponible pour GET, SET ou d'autres commandes ordinaires. Le résultat peut ressembler à des timeouts aléatoires de l'application car le pool se remplit lentement de connexions bloquées en mode abonné.

Utilisez des connexions séparées :

  • Un client ou pool normal pour les commandes Redis régulières.
  • Une connexion d'abonné de longue durée par processus ou thread d'abonné.
  • Une connexion d'éditeur séparée si votre bibliothèque cliente le recommande.

De nombreuses bibliothèques clientes fournissent une méthode duplicate() ou new connection pour cette raison. Utilisez-la. Ne supposez pas que le pool va s'en sortir.

Le comportement de reconnexion est important

Pub/Sub ne rejoue pas les messages manqués après une reconnexion. Si un abonné tombe pendant 30 secondes, chaque message publié pendant cette fenêtre est manqué par cet abonné.

C'est acceptable pour l'invalidation de cache uniquement si votre application a une solution de repli. Par exemple, un service qui manque un événement d'invalidation doit toujours avoir des TTL, des vérifications de version ou un moyen de reconstruire à partir de la base de données source. Sans solution de repli, une simple perturbation réseau peut laisser des entrées de cache local obsolètes trop longtemps.

Lors de la reconnexion, assurez-vous que le client se réabonne à tous les canaux et motifs. Certaines bibliothèques le font automatiquement. D'autres vous obligent à réenregistrer les gestionnaires après la reconnexion. Testez cela en tuant la connexion de l'abonné :

redis-cli CLIENT LIST | grep subscribe
redis-cli CLIENT KILL ID <client-id>

Puis publiez un message de test et confirmez que l'application le reçoit après la reconnexion.

Un fan-out élevé peut devenir une pression CPU et réseau

Le travail PUBLISH augmente avec le nombre d'abonnés et de motifs correspondants. Un canal avec des milliers d'abonnés signifie que Redis doit pousser le message des milliers de fois. Cela peut encore être acceptable, mais ce n'est pas gratuit.

Surveillez :

redis-cli INFO stats
redis-cli PUBSUB NUMSUB events:test
redis-cli PUBSUB NUMPAT

PUBSUB NUMSUB aide à confirmer si un canal a le nombre d'abonnés que vous pensez. PUBSUB NUMPAT est utile car les abonnements à des motifs larges peuvent vous surprendre. Quelques services utilisant PSUBSCRIBE * ou PSUBSCRIBE events:* peuvent recevoir beaucoup plus de trafic que prévu.

Si le trafic Pub/Sub est important et à fort volume, isolez-le. Une instance Redis dédiée pour Pub/Sub protège votre cache et votre magasin de sessions des problèmes de tampon d'abonné et des pics de diffusion. L'instance dédiée peut avoir une configuration adaptée à Pub/Sub, des préoccupations de persistance plus courtes et une surveillance axée sur les clients et la sortie réseau.

Gardez les gestionnaires de messages courts

Un abonné doit lire le message, effectuer une validation minimale et confier le travail à autre chose. Si le gestionnaire appelle une base de données, fait une requête HTTP, rend un modèle ou effectue un long calcul avant de lire le message suivant, il peut devenir le consommateur lent que vous essayez d'éviter.

Meilleure forme :

Message Redis -> gestionnaire léger -> file d'attente locale -> pool de workers

La file d'attente locale peut être une file d'attente intra-processus, un pool de threads, une file d'attente de tâches asynchrones ou un courtier durable si le travail doit survivre aux redémarrages de processus. L'idée clé est que la lecture depuis Redis ne doit pas attendre la dépendance en aval la plus lente.

Si le travail doit être fiable, Pub/Sub est la mauvaise primitive. Redis Streams avec des groupes de consommateurs vous donne des entrées en attente et des accusés de réception. Un courtier dédié peut être meilleur si vous avez besoin de files d'attente de lettres mortes, de nouvelles tentatives différées, de priorités ou d'une longue rétention.

Une liste de contrôle pratique pour le dépannage

Lorsque Pub/Sub semble malsain, je travaille généralement dans cet ordre :

  1. Confirmez le canal manuellement avec SUBSCRIBE et PUBLISH.
  2. Vérifiez CLIENT LIST pour les clients Pub/Sub avec un omem élevé.
  3. Inspectez client-output-buffer-limit pubsub.
  4. Vérifiez l'authentification et les autorisations de canal ACL.
  5. Confirmez que les abonnés utilisent des connexions dédiées.
  6. Tuez une connexion d'abonné et vérifiez le comportement de reconnexion et de réabonnement.
  7. Vérifiez PUBSUB NUMSUB et PUBSUB NUMPAT pour un fan-out inattendu.
  8. Décidez si la charge de travail a réellement besoin de Streams ou d'un courtier dédié.

Redis Pub/Sub n'est fiable que dans le sens où il fait exactement ce qu'il promet : une diffusion de messages en direct aux abonnés actuellement connectés. La plupart des incidents de production proviennent du fait de s'attendre à ce qu'il se comporte comme une file d'attente durable. Configurez les limites du tampon, isolez le trafic important et concevez les abonnés comme si les messages pouvaient être manqués, car parfois ils le peuvent.

Exemple d'incident : un tableau de bord ralentit le cache

Imaginez une instance Redis utilisée pour des clés de cache ordinaires et un petit canal Pub/Sub appelé metrics:live. Au début, seul un tableau de bord interne s'abonne. Des mois plus tard, plusieurs processus de passerelle de navigateur s'abonnent également, et l'un d'eux commence à envoyer des mises à jour via des connexions WebSocket lentes. Redis ne sait pas que le navigateur en aval est lent. Il voit seulement une connexion d'abonné qui ne lit pas assez vite.

Le premier symptôme peut ne pas mentionner Pub/Sub du tout. Les requêtes d'application qui utilisent des appels GET et SET normaux commencent à expirer. La mémoire augmente. L'hôte Redis semble occupé, mais SLOWLOG n'affiche pas de commande coûteuse évidente.

L'indice utile se trouve dans CLIENT LIST :

flags=P sub=1 omem=25165824 cmd=subscribe

Cela vous indique qu'un client Pub/Sub a un tampon de sortie important. Si la limite dure configurée est de 32 Mo, ce client est sur le point d'être déconnecté. Si la limite est désactivée, Redis peut continuer à mettre en mémoire tampon jusqu'à ce que l'ensemble de l'instance soit sous pression mémoire.

La solution ne consiste pas seulement à augmenter la limite. L'augmenter peut masquer le problème et permettre un pic de mémoire encore pire la prochaine fois. Une meilleure réponse est :

  1. Identifiez le processus d'abonné à partir de addr, name ou des métadonnées du client.
  2. Tuez le pire abonné si la mémoire est en danger.
  3. Ajoutez ou resserrez client-output-buffer-limit pubsub.
  4. Déplacez la livraison lente en aval dans une file d'attente interne en dehors de la boucle de lecture Redis.
  5. Envisagez de déplacer le Pub/Sub à fort volume vers une instance Redis dédiée.

Les noms de clients rendent cela beaucoup plus facile. De nombreuses bibliothèques Redis vous permettent de définir un nom de connexion. Utilisez des noms tels que web-1:pubsub-cache-invalidations ou dashboard:metrics-live afin que CLIENT LIST pointe vers le propriétaire au lieu de montrer uniquement une IP et un port.

CLIENT SETNAME dashboard:metrics-live

Cette petite hygiène transforme un incident de mémoire vague en une conversation directe avec l'équipe propriétaire de l'abonné.