Passer RabbitMQ à l'échelle : Guide d'optimisation des topologies de cluster

Concevez des clusters RabbitMQ qui passent à l'échelle sans confondre clustering, réplication et débit.

Passer RabbitMQ à l'échelle : Guide d'optimisation des topologies de cluster

Passer RabbitMQ à l'échelle commence par un fait inconfortable : un cluster n'est pas un broker magique plus gros. C'est un ensemble de brokers qui partagent des métadonnées et, selon le type de file d'attente, peuvent répliquer les données de la file. Si une seule file d'attente est surchargée, ajouter des nœuds autour peut améliorer la disponibilité, mais cela ne rendra pas automatiquement cette file d'attente plus rapide à consommer.

Cette distinction évite beaucoup de mauvaises conceptions. J'ai vu des équipes ajouter deux nœuds à un déploiement RabbitMQ chargé, ne rien déplacer, ne modifier aucune disposition de file d'attente, et se demander pourquoi la même file d'attente s'accumulait encore chaque après-midi. Le leader de la file était toujours sur le même nœud. Les mêmes consommateurs faisaient toujours le même travail. Le cluster avait plus de machines, mais le goulot d'étranglement n'avait pas bougé.

La topologie d'un cluster RabbitMQ consiste principalement à décider où vivent les files d'attente, combien de copies des messages importants vous avez besoin, et combien de pannes vous pouvez tolérer avant que le débit ne chute. La bonne réponse pour un pipeline de métriques éphémères n'est pas la même que pour les paiements, le traitement des commandes ou les événements d'audit.

Ce que le clustering partage réellement

Les nœuds RabbitMQ dans un cluster partagent des définitions : hôtes virtuels, utilisateurs, permissions, échanges, files d'attente, liaisons, politiques et métadonnées d'exécution nécessaires au fonctionnement du cluster. Un producteur connecté à un nœud peut publier sur un échange dont le leader de la file d'attente est sur un autre nœud. Un consommateur peut se connecter à un nœud différent de celui qui héberge la file d'attente.

Cela ne signifie pas que chaque message existe partout.

Les files d'attente classiques ont un leader sur un nœud. Les files d'attente quorum ont un leader plus des réplicas. Les flux ont leur propre modèle de réplication. Si un leader de file d'attente est distant de la plupart des clients qui l'utilisent, RabbitMQ doit déplacer le trafic à travers l'interconnexion du cluster. C'est acceptable avec modération. Cela devient coûteux lorsque chaque éditeur se connecte au nœud A, chaque file d'attente chaude vit sur le nœud B, et chaque consommateur se connecte au nœud C.

Une première règle simple fonctionne bien : connectez les applications aux nœuds proches des files d'attente qu'elles utilisent, ou placez un équilibreur de charge devant le cluster et vérifiez que les leaders des files d'attente sont raisonnablement équilibrés. Ne supposez pas que les connexions client en round-robin créent une charge round-robin sur les files d'attente.

Préférez trois nœuds avant de faire preuve de créativité

Pour la plupart des clusters RabbitMQ de production, trois nœuds dans une région à faible latence ou un groupe de zones de disponibilité est le point de départ propre. Cela donne aux files d'attente quorum un modèle majoritaire qui peut survivre à une panne de nœud, et maintient la coordination du cluster suffisamment simple pour raisonner lors d'un incident.

Les clusters à deux nœuds semblent moins chers, mais ils sont maladroits pour les files d'attente répliquées. Avec les systèmes basés sur le quorum, une majorité est requise. Si l'un des deux nœuds disparaît, il n'y a pas de majorité. Vous pouvez ajouter un troisième nœud de type témoin dans certains systèmes distribués, mais pour RabbitMQ, il est généralement plus simple et plus fiable d'exécuter trois vrais nœuds avec suffisamment de capacité disque et réseau.

Cinq nœuds peuvent avoir du sens lorsque vous avez beaucoup de files d'attente, avez besoin de plus d'options de placement, ou souhaitez répartir la charge sur plus de machines. Cela augmente également la quantité de communication du cluster et la surface opérationnelle. Avant de passer de trois à cinq, vérifiez si vous résolvez un problème de saturation de nœud ou de conception de file d'attente. Si une file d'attente est chaude, plus de nœuds seuls ne diviseront pas le travail de cette file.

Les files d'attente quorum sont pour la fiabilité répliquée, pas pour la vitesse gratuite

Pour les nouvelles charges de travail hautement disponibles, les files d'attente quorum sont généralement la valeur par défaut correcte lorsque la durabilité des messages est importante. Elles répliquent les messages en utilisant un protocole de consensus. Une file d'attente quorum avec trois membres peut continuer à fonctionner si un membre est indisponible, tant qu'une majorité reste saine.

Le compromis est le coût d'écriture. Un message persistant publié doit être répliqué à suffisamment de membres avant d'être considéré comme accepté en toute sécurité. C'est exactement ce que vous voulez pour un travail important, mais ce n'est pas le même profil de performance qu'une file d'attente classique transitoire.

Déclarez une file d'attente quorum avec un argument ou une politique, selon la façon dont votre application gère la topologie :

rabbitmqadmin declare queue name=orders durable=true arguments='{"x-queue-type":"quorum"}'

Pour les politiques, limitez-les soigneusement. Ne convertissez pas accidentellement chaque file d'attente dans un hôte virtuel en file d'attente quorum simplement parce qu'un modèle large correspond à .*. Un bon nom de politique et un préfixe de file d'attente étroit sont ennuyeux de la meilleure façon :

rabbitmqctl set_policy qq-orders '^orders\.' '{"queue-type":"quorum"}' --apply-to queues

Si vous migrez depuis des files d'attente classiques mises en miroir, traitez cela comme une migration, pas un changement de drapeau. Les files d'attente classiques mises en miroir et les files d'attente quorum se comportent différemment en ce qui concerne l'ordre, les messages empoisonnés, l'utilisation de la mémoire et le basculement. Créez le nouveau type de file d'attente, acheminez une tranche contrôlée de trafic, surveillez les confirmations et la latence des consommateurs, puis déplacez le reste.

Les files d'attente classiques ont encore leur place

Les files d'attente classiques sont toujours utiles pour les charges de travail où la réplication n'est pas requise, où les messages sont transitoires, ou où la file d'attente est locale à un service et peut être reconstruite à partir d'une autre source. Elles sont également un choix raisonnable pour les événements à volume élevé et de faible valeur où perdre quelques messages lors d'une panne de nœud est acceptable.

Utilisez les files d'attente classiques délibérément. Si une file d'attente classique est durable et reçoit des messages persistants, ces messages sont stockés sur le nœud hébergeant cette file. Si ce nœud est en panne, la file d'attente est indisponible jusqu'à ce que le nœud revienne. Cela peut être acceptable pour un travail de réconciliation en arrière-plan. Ce n'est généralement pas acceptable pour l'état de commande visible par le client.

Pour les longs arriérés, demandez-vous si la charge de travail devrait être un flux ou un système de stockage différent. RabbitMQ peut contenir des files d'attente, mais une file d'attente avec des millions de vieux messages est souvent le signe que les consommateurs sont sous-dimensionnés, que les systèmes en aval échouent, ou que le processus métier a besoin de sémantiques de rejeu plutôt que de sémantiques de file d'attente.

Mettez des limites de latence autour du cluster

Le clustering RabbitMQ s'attend à des liens réseau fiables et à faible latence. Étirer un seul cluster à travers des régions éloignées est généralement un mauvais compromis. Le trafic inter-nœuds devient plus lent, le basculement devient plus difficile à prévoir, et une partition réseau peut être plus dommageable que la panne que vous essayiez d'éviter.

Une conception pratique est un cluster RabbitMQ par région, avec un routage au niveau de l'application ou une fédération/une navette entre les régions lorsque vous avez besoin de mouvement inter-régional. Cela maintient la publication et la consommation locales rapides. Cela rend également les domaines de défaillance clairs : si la région A est en mauvaise santé, la région B n'est pas entraînée dans le même problème d'appartenance au cluster.

Le multi-AZ à l'intérieur d'une seule région est différent. Si la latence entre les zones est faible et stable, trois nœuds répartis sur trois zones peuvent bien fonctionner. Testez-le sous charge réelle. Le fait qu'un fournisseur de cloud appelle quelque chose une zone de disponibilité ne vous dit pas comment vos tailles de messages, confirmations et files d'attente quorum se comporteront pendant une heure chargée.

Équilibrez les leaders des files d'attente, pas seulement les nœuds

Un cluster peut sembler équilibré au niveau du graphique CPU et être encore fortement déséquilibré au niveau de la file d'attente. Un nœud peut posséder les leaders des files d'attente les plus chargées tandis que les autres détiennent principalement des réplicas silencieux.

Vérifiez le placement des files d'attente :

rabbitmqctl list_queues name type leader members messages_ready messages_unacknowledged

Si un nœud possède la plupart des leaders chauds, déplacez ou rééquilibrez les files d'attente en utilisant les outils pris en charge par RabbitMQ pour votre version et votre type de file. Pour les files d'attente quorum, le placement des membres et l'emplacement du leader sont importants. Pour les files d'attente classiques, le placement du maître de file est important dans la terminologie plus ancienne, bien que les versions plus récentes utilisent le terme leader de manière plus cohérente.

Une bonne topologie répartit les files d'attente chaudes non liées entre les nœuds. Par exemple, email.send, image.resize et billing.capture ne devraient pas tous être dirigés par le même nœud si chacun a un trafic lourd. Si billing.capture est la seule file d'attente chaude, divisez par un shard métier réel tel que le groupe de commerçants ou la région uniquement si les consommateurs peuvent traiter ces shards en toute sécurité de manière indépendante.

Concevez le comportement de connexion client

Le placement des connexions client fait partie de la topologie. Si chaque application se connecte au premier résultat DNS pour toujours, un nœud peut porter la plupart du trafic client même lorsque les files d'attente sont réparties sur le cluster. Un équilibreur de charge peut aider, mais il doit utiliser des vérifications de santé qui comprennent si un nœud est réellement disponible pour le trafic AMQP.

Gardez les connexions de longue durée. RabbitMQ peut gérer de nombreuses connexions, mais le renouvellement des connexions consomme du CPU, de la mémoire, des descripteurs de fichiers et des frais généraux TLS. Une requête web ne devrait pas ouvrir une nouvelle connexion AMQP, publier un message et la fermer. Utilisez un pool de connexions ou de canaux approprié pour la bibliothèque client.

Décidez également ce que les clients font lors d'une panne de nœud. Les bons clients se reconnectent avec backoff, rouvrent les canaux, redéclarent la topologie privée si nécessaire, et reprennent les confirmations ou la consommation avec précaution. Les mauvais clients se reconnectent en boucles serrées et transforment un redémarrage de nœud en une tempête de connexions.

Pour les consommateurs, pensez à la localité mais évitez le sur-ajustement. Connecter un consommateur au même nœud que son leader de file d'attente peut réduire le trafic inter-nœuds, mais les leaders de file peuvent se déplacer après des pannes. Le consommateur devrait survivre à cela sans modifications manuelles.

Les partitions sont des événements opérationnels, pas seulement des paramètres

Les partitions réseau sont là où les diagrammes de cluster sont testés. RabbitMQ a des modes de gestion des partitions, mais aucun paramètre ne supprime le besoin d'une décision opérationnelle claire. Si deux côtés d'un cluster ne peuvent pas communiquer, vous devez décider si la disponibilité ou la cohérence est plus importante pour cette charge de travail.

Les files d'attente quorum nécessitent une majorité de réplicas. C'est le but : un côté minoritaire ne devrait pas continuer à accepter des écritures qui ne peuvent pas être convenues en toute sécurité. Cela peut surprendre les équipes qui s'attendaient à ce que chaque nœud survivant reste accessible en écriture. Planifiez pour cela. Placez les membres des files d'attente quorum là où une majorité peut survivre aux pannes qui vous intéressent.

N'étalez pas une file d'attente quorum à trois nœuds sur trois régions éloignées et ne vous attendez pas à un comportement fluide pendant la latence Internet normale. Le quorum ne sera aussi agréable que le réseau entre les membres. Une faible latence et une faible perte de paquets sont des exigences de capacité, pas des options agréables.

Effectuez des exercices de partition dans un environnement non productif. Bloquez le trafic entre les nœuds, observez quelles files d'attente restent disponibles, regardez les clients se reconnecter, et notez les étapes de récupération. La première fois que vous apprenez le comportement de votre partition ne devrait pas être lors d'un véritable incident réseau.

Passez les consommateurs à l'échelle avant les brokers

Lorsque le symptôme est une file d'attente qui grossit, le broker n'est pas toujours le goulot d'étranglement. Souvent, les consommateurs sont simplement plus lents que les éditeurs. Avant d'ajouter des nœuds RabbitMQ, vérifiez l'utilisation des consommateurs, les comptes non acquittés, le temps de traitement et la latence en aval.

Si les messages sont prêts mais pas non acquittés, RabbitMQ a des messages en attente et les consommateurs ne les prennent pas assez vite. Ajoutez des consommateurs, corrigez le préfetch, ou supprimez les retards en aval. Si les messages sont principalement non acquittés, les consommateurs ont déjà reçu du travail et mettent trop de temps à l'acquitter. Ajouter des nœuds broker ne rendra pas ces gestionnaires plus rapides.

Le préfetch est important ici. Un préfetch de 500 sur un travailleur lent peut cacher un arriéré à l'intérieur des processus consommateurs. Un préfetch de 1 sur un travailleur local rapide peut perdre du temps en allers-retours. Commencez avec une petite valeur, mesurez la latence de bout en bout et la mémoire du consommateur, puis ajustez.

Surveillez les limites ennuyeuses

Les plans de mise à l'échelle parlent souvent de topologie et oublient les descripteurs de fichiers, les alarmes de disque, les alarmes mémoire, le renouvellement des connexions et les nombres de canaux. RabbitMQ est sensible à tous.

Pour chaque nœud, surveillez la mémoire utilisée, l'espace disque libre, les descripteurs de fichiers, les sockets, la mémoire des processus de file d'attente, les taux de messages, la latence de confirmation et l'utilisation du planificateur Erlang. Côté client, surveillez les boucles de reconnexion et les taux de création de canaux. Un service qui ouvre une nouvelle connexion pour chaque publication peut nuire à un cluster bien avant que le volume de messages ne semble impressionnant.

Utilisez des connexions et des canaux de longue durée là où votre bibliothèque client les prend en charge. Mettez les limites de connexion et les paramètres de heartbeat dans la conception, pas dans un changement de panique lors d'une panne.

Une topologie qui fonctionne généralement

Pour une application métier typique, je commencerais avec trois nœuds RabbitMQ dans une région, répartis sur des zones si le réseau est bon. Utilisez des files d'attente quorum pour les flux de travail durables importants. Utilisez des files d'attente classiques pour le travail transitoire où le comportement en cas de panne est acceptable. Gardez les éditeurs et les consommateurs proches du cluster. Utilisez un équilibreur de charge pour l'accès client, mais vérifiez l'équilibre des leaders de file d'attente plutôt que de supposer que l'équilibreur de charge a résolu le placement du broker.

Ensuite, testez les cas laids : tuez un nœud, mettez en pause un groupe de consommateurs, remplissez une file d'attente, ralentissez le disque, et redémarrez un éditeur. Passer RabbitMQ à l'échelle concerne moins le plus joli diagramme et plus la connaissance de ce qui se passe lorsque le diagramme est stressé.