Maximiser le débit des messages : modes d'accusé de réception automatique vs manuel
Atteindre un débit de messages optimal dans RabbitMQ nécessite de maîtriser les modes d'accusé de réception. Ce guide compare les stratégies d'accusé de réception automatique (Auto-Ack) et manuelle, détaillant comment l'Auto-Ack sacrifie la sécurité des messages pour la vitesse brute. Apprenez à optimiser les performances en comprenant le rôle crucial des paramètres de prélecture (QoS) des consommateurs pour maximiser le débit tout en maintenant des garanties de livraison essentielles pour les systèmes à volume élevé.
Maximiser le débit des messages : modes d'accusé de réception automatique vs manuel
Le mode d'accusé de réception de RabbitMQ est l'un de ces paramètres qui semble insignifiant dans le code client mais qui a d'énormes conséquences opérationnelles. Il décide quand le courtier est autorisé à oublier un message. Ce choix affecte le débit, la pression mémoire, les tentatives, les doublons de travail et ce qui se produit lorsqu'un consommateur plante en cours de traitement.
La version courte est la suivante : l'accusé de réception automatique est rapide car RabbitMQ considère le message comme traité dès qu'il est livré. L'accusé de réception manuel est plus sûr car votre consommateur indique explicitement à RabbitMQ quand le traitement a réussi. La plupart des systèmes de production devraient commencer par des accusés de réception manuels et ajuster la prélecture avant même d'envisager l'Auto-Ack.
Ce que signifie réellement un accusé de réception
Un accusé de réception n'est pas un reçu métier. C'est un signal au niveau du courtier. Lorsqu'un consommateur envoie basic.ack, il indique à RabbitMQ : cette livraison peut être supprimée de la file d'attente.
Cette distinction est importante. Si votre consommateur écrit une commande dans une base de données, envoie un e-mail et met à jour un index de recherche, le point d'accusé de réception correct se situe généralement après que la partie durable du travail a réussi. Si vous accusez réception avant la validation de la base de données et que le processus plante, RabbitMQ a fait exactement ce que vous avez demandé : il a supprimé le message. Votre application a perdu le travail.
Accusé de réception automatique
Avec l'Auto-Ack, le client s'abonne avec l'accusé de réception automatique activé. RabbitMQ envoie un message et le traite immédiatement comme livré avec succès. Le consommateur n'envoie pas de basic.ack ultérieur.
Dans de nombreuses bibliothèques clientes, le paramètre apparaît comme un booléen lors de la consommation. Par exemple, Java utilise autoAck dans basicConsume ; plusieurs bibliothèques exposent la même idée avec des noms légèrement différents.
L'attrait est évident. Il y a moins d'opérations de protocole et moins de comptabilité. Un consommateur peut accepter les messages aussi rapidement que RabbitMQ et le réseau peuvent les livrer. Pour la télémétrie, les mises à jour de progression transitoires ou les charges de travail jetables, cela peut être acceptable.
Le risque est également évident une fois que vous l'avez vu en production. Si le consommateur reçoit dix mille messages puis plante avant de traiter son tampon en mémoire, ces messages sont perdus de la file d'attente. RabbitMQ ne peut pas les redistribuer car ils ont déjà été accusés automatiquement.
L'Auto-Ack est raisonnable lorsque le message n'est pas critique, peut être régénéré ou représente un flux en direct où les anciennes données ne sont pas utiles. Les exemples incluent les métriques au mieux, les mises à jour de présence d'interface utilisateur ou les événements de type journal où un pipeline durable séparé est la source d'enregistrement. C'est un mauvais choix pour les paiements, les commandes, les changements d'inventaire, les mises à jour de compte ou les travaux où un message manqué crée un nettoyage manuel.
Accusé de réception manuel
Avec l'accusé de réception manuel, RabbitMQ conserve les messages livrés dans un état non accusé jusqu'à ce que le consommateur réponde. Si la connexion du consommateur se ferme avant l'accusé de réception, RabbitMQ remet en file d'attente ces messages non accusés et peut les livrer à nouveau.
Ce comportement est la raison pour laquelle l'accusé de réception manuel est la valeur par défaut normale pour les travaux importants. Cela ne signifie pas un traitement exactement une fois. Un message peut être traité, puis le consommateur peut planter avant d'envoyer l'accusé de réception. RabbitMQ le redistribuera et votre application peut voir le même travail logique deux fois. L'accusé de réception manuel vous offre une livraison au moins une fois, donc votre gestionnaire a toujours besoin d'idempotence là où des effets secondaires en double seraient préjudiciables.
Une boucle de consommateur sécurisée suit généralement cette forme :
recevoir le message
valider la charge utile
effectuer le travail durable
valider la transaction de base de données ou l'effet secondaire externe
accuser réception du message
Pour les échecs, décidez si le message doit être réessayé, retardé ou mis en lettre morte. Remettre en file d'attente chaque échec immédiatement peut créer une boucle active où le même mauvais message brûle du CPU toute la journée. Un échange de lettres mortes, une file d'attente de tentatives ou un modèle de tentatives retardées est souvent meilleur.
La prélecture est le véritable levier de débit
De nombreuses équipes comparent l'Auto-Ack et l'accusé de réception manuel, constatent que l'accusé de réception manuel est plus lent avec les paramètres par défaut et tirent la mauvaise conclusion. La pièce manquante est la prélecture.
La prélecture de RabbitMQ, configurée avec basic.qos, limite le nombre de messages non accusés qu'un consommateur peut détenir à la fois. Avec l'accusé de réception manuel et prefetch=1, un consommateur reçoit un message, le traite, l'accuse, puis n'en reçoit un autre qu'ensuite. C'est sûr, mais cela laisse du débit sur la table pour tout travailleur capable de traiter simultanément ou de tolérer un petit tampon local.
Une prélecture plus élevée permet à RabbitMQ de garder le consommateur occupé :
prefetch = worker_concurrency * expected_work_buffer
Si un travailleur traite 8 tâches simultanément, une prélecture de 16 ou 32 est un point de départ raisonnable. Si chaque message est volumineux ou si le traitement est lourd en mémoire, commencez plus bas. Si chaque message est minuscule et que le traitement est principalement des E/S réseau, un nombre plus élevé peut aider.
Ne copiez pas une prélecture aléatoire de 250 dans chaque service. Une prélecture élevée peut provoquer une distribution inégale. Un consommateur peut recevoir un lot important et le conserver tandis que d'autres consommateurs restent inactifs. Cela augmente également les rafales de redistribution lorsqu'un consommateur meurt. RabbitMQ remettra en file d'attente toutes les livraisons non accusées de cette connexion, ce qui peut faire hériter soudainement à un autre travailleur d'un arriéré important.
Compromis entre débit et sécurité
Voici la comparaison pratique :
| Mode | Ce que fait RabbitMQ | Force | Risque principal |
|---|---|---|---|
| Auto-Ack | Supprime le message à la livraison | Taux de livraison brut le plus élevé | Travail perdu si le consommateur plante |
| Ack manuel, faible prélecture | Attend chaque ack avant d'en envoyer beaucoup plus | Comportement d'échec simple | Consommateurs sous-utilisés |
| Ack manuel, prélecture ajustée | Maintient un nombre contrôlé de messages en vol | Bon débit avec récupération | Nécessite des gestionnaires idempotents et une conception de tentatives |
Le détail important est que l'accusé de réception manuel n'a pas à être lent. Un accusé de réception manuel mal réglé est lent. Un accusé de réception manuel avec une prélecture raisonnable, des travailleurs simultanés et des transactions de base de données courtes peut gérer un volume sérieux tout en préservant le comportement de récupération.
Un workflow de réglage concret
Commencez par un accusé de réception manuel et une prélecture prudente :
prefetch = 1 à 4 par thread de travailleur
Mesurez l'utilisation du consommateur, la profondeur de la file d'attente, le temps de traitement des messages, la mémoire et les redistributions. Si les consommateurs sont inactifs alors que la file d'attente contient des messages, augmentez la prélecture. Si la mémoire augmente ou qu'un consommateur accapare le travail, diminuez-la. Si les redistributions augmentent, inspectez les plantages, les délais d'attente et le comportement de nack avant de modifier à nouveau la prélecture.
Surveillez également le courtier. Un débit élevé n'est pas seulement un nombre de consommateurs. Les E/S disque, les confirmations d'éditeur, le type de file d'attente, la taille des messages, la durabilité, la mise en miroir ou la réplication de quorum, et la bande passante réseau affectent tous le résultat. Le mode d'accusé de réception est un levier dans un système plus vaste.
La gestion des erreurs est plus importante que le drapeau
Un consommateur à accusé de réception manuel sans plan d'échec n'est qu'à moitié construit. En cas de succès, accusez réception. En cas d'échec temporaire, nack et remettez en file d'attente uniquement si une nouvelle tentative immédiate a du sens. En cas de message empoisonné, rejetez ou nack sans remettre en file d'attente et acheminez-le vers un échange de lettres mortes s'il est configuré.
Définissez également une politique de tentatives maximales en dehors de la file d'attente principale du consommateur. RabbitMQ ne saura pas magiquement qu'un message JSON mal formé a échoué 5 fois, à moins que votre conception ne suive les tentatives via des en-têtes, des files d'attente de tentatives ou l'état de l'application.
Ce que je choisirais par défaut
Pour les événements métier et les tâches d'arrière-plan, utilisez des accusés de réception manuels. Ajustez la prélecture en fonction de la concurrence des travailleurs et de la mémoire. Rendez les gestionnaires idempotents. Ajoutez la mise en lettre morte avant qu'un mauvais message ne vous apprenne pourquoi les tentatives immédiates infinies sont douloureuses.
Utilisez l'Auto-Ack uniquement lorsque la perte est acceptable et documentée. Cette phrase devrait être facile à défendre lors d'une revue d'incident. Si l'équipe serait contrariée de découvrir qu'un message livré mais non traité a disparu, l'Auto-Ack est le mauvais paramètre.
La taille du message change la réponse
Une valeur de prélecture qui fonctionne parfaitement pour des messages de 2 Ko peut être imprudente pour des messages de 5 Mo. La prélecture contrôle le nombre, pas le total d'octets. Si un consommateur peut détenir 100 messages non accusés et que chaque message est volumineux, l'empreinte mémoire locale peut augmenter rapidement. Le courtier doit également suivre ces livraisons jusqu'à ce qu'elles soient accusées.
Lorsque les messages sont volumineux, commencez avec une prélecture plus faible et mesurez la mémoire résidente dans le processus consommateur. Si possible, gardez le corps du message petit et stockez les charges utiles volumineuses ailleurs, comme le stockage d'objets, le message portant une référence et une somme de contrôle. Cette conception n'est pas toujours appropriée, mais elle empêche le courtier de devenir un transport de fichiers volumineux.
L'accusé de réception par lots peut réduire le bavardage du protocole
De nombreuses bibliothèques clientes vous permettent d'accuser réception de plusieurs livraisons avec un seul ack en utilisant le drapeau multiple. Cela peut réduire la surcharge du protocole lorsqu'un consommateur traite les messages dans l'ordre et peut accuser réception en toute sécurité d'une plage d'étiquettes de livraison.
L'inconvénient est la gestion des échecs. Si vous traitez les messages simultanément, l'ordre des étiquettes de livraison peut ne pas correspondre à l'ordre d'achèvement. Accuser réception de plusieurs messages parce que le dernier a réussi peut accidentellement accuser réception de messages antérieurs qui sont toujours en cours d'exécution ou ont échoué. Pour les travailleurs simultanés, l'accusé de réception par message est souvent plus simple et plus sûr.
Une règle utile : n'accusez réception par lots que lorsque le modèle de traitement du consommateur est suffisamment ordonné pour que vous puissiez expliquer exactement quels messages sont couverts par l'accusé de réception.
Surveillez les messages non accusés lors des incidents
RabbitMQ expose les comptes de messages prêts et non accusés. Une file d'attente avec de nombreux messages prêts signifie que les consommateurs ne suivent pas ou ne sont pas connectés. Une file d'attente avec de nombreux messages non accusés signifie que RabbitMQ a livré du travail aux consommateurs mais n'a pas encore reçu d'accusés de réception.
Ce deuxième cas vous oriente vers le comportement du consommateur : traitement lent, appels externes bloqués, prélecture trop élevée, threads bloqués ou un consommateur qui a cessé d'accuser après une exception. C'est différent d'un éditeur inondant la file d'attente plus rapidement que les consommateurs ne peuvent recevoir.
Avec l'interface de gestion ou rabbitmqctl, regardez :
rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers
Si messages_unacknowledged est élevé et que les consommateurs sont en vie, vérifiez les journaux des consommateurs et les vidages de threads avant de modifier les paramètres du courtier. Le courtier peut simplement attendre que l'application termine son travail.
La redistribution est normale, mais la redistribution répétée est un signe de problème
L'accusé de réception manuel signifie que les messages peuvent être redistribués après une défaillance du consommateur. C'est attendu. Ce que vous ne voulez pas, c'est que le même message empoisonné soit livré, échoue, remis en file d'attente et livré à nouveau pour toujours.
Ajoutez suffisamment de métadonnées pour diagnostiquer les tentatives. Certaines équipes utilisent des en-têtes pour suivre les tentatives. D'autres déplacent les échecs vers un échange de tentatives, puis vers une file d'attente de lettres mortes après une limite. Le modèle exact varie, mais l'objectif opérationnel est le même : les échecs temporaires ont une autre chance, les échecs permanents deviennent visibles et cessent de bloquer le travail utile.
Lorsqu'un gestionnaire n'est pas idempotent, la redistribution devient dangereuse. Supposons qu'un travailleur facture une carte, puis plante avant d'accuser réception. RabbitMQ redistribuera le message. Si le gestionnaire facture à nouveau, le courtier n'a pas créé le bug ; il a révélé une clé d'idempotence manquante. Pour les effets secondaires externes, stockez un identifiant d'opération durable et rendez l'effet secondaire sûr à répéter.
Les confirmations d'éditeur sont une préoccupation distincte
Les accusés de réception des consommateurs indiquent à RabbitMQ que les consommateurs ont géré les livraisons. Les confirmations d'éditeur indiquent aux éditeurs que RabbitMQ a accepté les messages publiés. Ils résolvent des côtés opposés du flux.
Un système peut utiliser un accusé de réception manuel du consommateur et toujours perdre des messages au moment de la publication si les éditeurs tirent et oublient sans confirmations et que la connexion tombe au mauvais moment. De même, les confirmations d'éditeur ne protègent pas le travail après qu'un consommateur a reçu un message. Pour les pipelines fiables, utilisez les deux là où le cas métier l'exige : des confirmations du côté de la publication, un accusé de réception manuel du côté de la consommation, des files d'attente durables le cas échéant, et un traitement idempotent au niveau de l'application.
Le type de file d'attente et la durabilité affectent la même discussion sur le débit
Le mode d'accusé de réception n'existe pas isolément. Une file d'attente classique transitoire avec des messages non persistants a un profil de performances et de sécurité différent d'une file d'attente de quorum durable avec des messages persistants. Si vous évaluez l'Auto-Ack sur une file d'attente jetable, puis appliquez le résultat à une file d'attente de production durable, la comparaison n'est pas utile.
Pour les charges de travail importantes, les files d'attente durables et les messages persistants sont courants, mais ils ajoutent du travail de disque et de réplication. Les files d'attente de quorum améliorent la sécurité des données par rapport aux anciens modèles de files d'attente classiques mis en miroir, mais elles modifient également les caractéristiques de débit. Mesurez le type de file d'attente que vous exécutez réellement.
Un test équitable maintient ces variables stables :
même taille de message
même type de file d'attente
mêmes paramètres de durabilité
même comportement de confirmation d'éditeur
même nombre de consommateurs
même prélecture
même traitement en aval
Ne modifiez qu'un seul levier à la fois. Sinon, vous ne saurez pas si le résultat provient du mode d'accusé de réception, de la prélecture, du type de file d'attente, de la taille du message ou du code du consommateur.
La concurrence du consommateur doit correspondre au travail
Si chaque message passe la plupart de son temps à attendre HTTP ou une base de données, un consommateur peut bénéficier d'un traitement simultané. Si chaque message est lourd en CPU, trop de concurrence peut ralentir chaque message. La prélecture doit suivre cette réalité.
Pour un consommateur monothread, une prélecture de 100 peut simplement créer une grande salle d'attente locale. Pour un travailleur avec 20 emplacements de traitement actifs, une prélecture de 40 peut garder ces emplacements alimentés. Pour un processus lié au CPU avec quatre cœurs, une concurrence de 100 peut augmenter le changement de contexte sans améliorer le débit.
Mesurez le temps de traitement à l'intérieur du consommateur, pas seulement la profondeur de la file d'attente. Ajoutez des journaux ou des métriques pour l'heure de réception, l'heure de début, l'heure de fin, l'heure d'accusé de réception, la raison de l'échec et le drapeau de redistribution. Ces horodatages facilitent grandement la détermination si le travail attend dans RabbitMQ, à l'intérieur du consommateur ou est bloqué dans un système en aval.