
Nous avons préparé la traduction de la partie suivante de l'article en série, qui compare les fonctionnalités d'Apache Kafka et de RabbitMQ. Cette publication traite de la sémantique et des garanties de livraison des messages. Veuillez noter que l'auteur a pris en compte Kafka jusqu'à la version 0.10 incluse, et qu'il est apparu une seule fois dans la version 0.11. Néanmoins, l'article reste pertinent et regorge de points utiles d'un point de vue pratique.
Parties précédentes:
première ,
deuxième .
RabbitMQ et Kafka offrent des garanties de livraison de messages fiables. Les deux plates-formes offrent des garanties selon les principes de la «livraison unique au maximum» et de la «livraison unique au moins», mais avec le principe de la «livraison strictement ponctuelle», les garanties de Kafka s'appliquent selon un scénario très limité.
Voyons d'abord ce que ces garanties signifient:
- Livraison au plus une fois. Cela signifie que le message ne peut pas être remis plus d'une fois. Cependant, le message peut être perdu.
- Livraison au moins une fois. Cela signifie que le message ne sera jamais perdu. Dans ce cas, le message peut être remis plusieurs fois.
- Livraison une seule fois. Le Saint Graal des systèmes de messagerie. Tous les messages sont remis strictement une fois.
Le mot «livraison» ici n'est probablement pas un terme exact. Il serait plus exact de dire «traitement». Dans tous les cas, ce qui nous intéresse maintenant, c'est de savoir si le consommateur peut traiter les messages et selon quel principe cela se produit: «pas plus d'un», «au moins un» ou «strictement une fois». Mais le mot «traitement» complique la perception, et l'expression «remise selon le principe« strictement une fois »» dans ce cas ne sera pas une définition précise, car il peut être nécessaire de livrer le message deux fois afin de le traiter correctement une fois. Si le destinataire s'est déconnecté pendant le traitement, le message doit être envoyé à nouveau au nouveau destinataire.
Le deuxième. En discutant de la question du traitement des messages, nous arrivons au sujet des échecs partiels, ce qui est un casse-tête pour les développeurs. Le processus de traitement du message comporte plusieurs étapes. Il consiste en des sessions de communication entre l'application et le système de messagerie au début et à la fin et l'application elle-même travaillant avec les données au milieu. Les scénarios de défaillance partielle d'une application doivent être gérés par l'application elle-même. Si les opérations effectuées sont entièrement transactionnelles et que les résultats sont formulés sur le principe du «tout ou rien», des défaillances partielles dans la logique d'application peuvent être évitées. Mais souvent, de nombreuses étapes incluent l'interaction avec d'autres systèmes, où la transactionnalité est impossible. Si nous incluons dans l'interaction les relations entre les systèmes de messagerie, les applications, le cache et la base de données, pouvons-nous garantir le traitement «une seule fois»? La réponse est non.
La stratégie «strictement unique» se limite à un scénario dans lequel le seul destinataire des messages traités est la plateforme de messagerie elle-même, et cette plateforme elle-même fournit des transactions complètes. Dans ce scénario limité, vous pouvez traiter des messages, les écrire, envoyer des signaux indiquant qu'ils ont été traités dans le cadre d'une transaction effectuée sur le principe du «tout ou rien». Il est fourni par la bibliothèque Kafka Streams.
Mais si le traitement des messages est toujours idempotent, vous pouvez éviter d'avoir à mettre en œuvre la stratégie «strictement une fois» via les transactions. Si le traitement final des messages est idempotent, vous pouvez vous inquiéter d'accepter les doublons. Mais toutes les actions ne peuvent pas être mises en œuvre de manière identique.
Alerte de bout en boutCe qui n'est représenté sur aucun appareil de tous les systèmes de messagerie avec lesquels j'ai travaillé, c'est la confirmation de bout en bout. Étant donné qu'un message peut être mis en file d'attente dans RabbitMQ, la notification de bout en bout n'a pas de sens. Sur Kafka, de même, plusieurs groupes différents de destinataires peuvent lire simultanément des informations à partir d'un sujet. D'après mon expérience, les alertes de bout en bout sont ce que les personnes novices en matière de messagerie demandent souvent. Dans de tels cas, il est préférable d'expliquer immédiatement que cela n'est pas possible.
Chaîne de responsabilitéDans l'ensemble, les sources de messages ne peuvent pas savoir que leurs messages sont remis aux destinataires. Ils peuvent seulement savoir que le système de messagerie a reçu leurs messages et a pris la responsabilité d'assurer leur stockage et leur livraison en toute sécurité. Il existe une chaîne de responsabilité qui commence par la source, passe par le système de messagerie et se termine chez le destinataire. Chacun doit remplir correctement ses fonctions et transmettre clairement le message au suivant. Cela signifie que vous, en tant que développeur, devez correctement concevoir vos applications afin d'éviter la perte ou l'utilisation abusive des messages pendant qu'ils sont sous votre contrôle.
Procédure de transfert des messagesCet article est principalement consacré à la façon dont chaque plate-forme fournit des stratégies d'envoi «au moins une» et «pas plus d'une». Mais il y a toujours un ordre de messagerie. Dans les parties précédentes de cette série, j'ai écrit sur l'ordre dans lequel les messages sont envoyés et l'ordre dans lequel ils sont traités, et je vous conseille de vous référer à ces parties.
En bref, RabbitMQ et Kafka offrent une garantie premier entré, premier sorti (FIFO). RabbitMQ conserve cet ordre au niveau de la file d'attente et Kafka au niveau de la segmentation. Les implications de telles décisions de conception ont été discutées dans les articles précédents.
Garanties de livraison dans RabbitMQLes garanties de livraison sont fournies:
- fiabilité des messages - ils ne disparaissent pas lorsqu'ils sont stockés sur RabbitMQ;
- notifications de messages - RabbitMQ échange des signaux avec les expéditeurs et les destinataires.
Éléments de fiabilité
Mise en miroir de la file d'attenteLes files d'attente peuvent être mises en miroir (répliquées) sur de nombreux nœuds (serveurs). Chaque file d'attente a une file d'attente principale à l'un des nœuds. Par exemple, il y a trois nœuds, 10 files d'attente et deux répliques par file d'attente. 10 files d'attente de contrôle et 20 répliques seront réparties sur trois nœuds. La répartition des files d'attente de contrôle par nœuds peut être configurée. En cas de gel d'un nœud:
- au lieu de chaque file d'attente principale sur le nœud bloqué, une réplique de cette file d'attente est fournie sur un autre nœud;
- de nouvelles répliques sont créées sur d'autres nœuds pour remplacer les répliques perdues sur le nœud sortant, prenant ainsi en charge le facteur de réplication.
La question de la tolérance aux pannes sera discutée dans la prochaine partie de l'article.
Files d'attente approuvéesIl existe deux types de files d'attente sur RabbitMQ: fiable et non fiable. Les files d'attente fiables sont écrites sur le disque et enregistrées en cas de redémarrage du nœud. Lorsque le nœud démarre, ils sont remplacés.
Messages persistantsSi la file d'attente est fiable, cela ne signifie pas que ses messages sont enregistrés au redémarrage du nœud. Seuls les messages marqués comme persistants par l'expéditeur seront récupérés.
Lorsque vous travaillez sur RabbitMQ, plus le message est fiable, plus les performances possibles sont faibles. S'il existe un flux d'événements en temps réel et qu'il n'est pas essentiel d'en perdre plusieurs ou un petit intervalle de temps du flux, il est préférable de ne pas utiliser la réplication de file d'attente et de transmettre tous les messages comme instables. Mais s'il n'est pas souhaitable de perdre des messages en raison d'un dysfonctionnement d'un nœud, il est préférable d'utiliser des files d'attente fiables avec réplication et messages stables.
Notifications de message
MessagerieLes messages peuvent être perdus ou dupliqués pendant la transmission. Cela dépend du comportement de l'expéditeur.
"Tiré et oublié"La source peut décider de ne pas demander de confirmation au destinataire (notification de réception d'un message à l'expéditeur) et simplement envoyer le message automatiquement. Les messages ne seront pas dupliqués, mais peuvent être perdus (ce qui satisfait la stratégie «au maximum pour une livraison unique»).
Confirmations à l'expéditeurLorsque l'expéditeur ouvre un canal pour le courtier de files d'attente, il peut utiliser le même canal pour envoyer des confirmations. Maintenant, en réponse au message reçu, le courtier de files d'attente doit fournir l'une des deux choses suivantes:
- basic.ack. Confirmation positive. Le message est reçu, la responsabilité en incombe désormais à RabbitMQ;
- basic.nack. Confirmation négative. Quelque chose s'est produit et le message n'a pas été traité. La responsabilité en demeure à la source. Si vous le souhaitez, il peut envoyer un message une deuxième fois.
En plus des notifications de livraison positives et négatives, un message de retour de base est fourni. Parfois, l'expéditeur doit savoir non seulement que le message est arrivé dans RabbitMQ, mais également qu'il est vraiment tombé dans une ou plusieurs files d'attente. Il peut arriver que la source envoie un message au système d'échange de rubriques, dans lequel le message n'est acheminé vers aucune des files d'attente de remise. Dans cette situation, le courtier rejette simplement le message. Dans certains scénarios, cela est normal, dans d'autres, la source doit savoir si le message a été rejeté et poursuivre en conséquence. Vous pouvez définir l'indicateur «Obligatoire» pour les messages individuels, et si le message n'a été défini dans aucune file d'attente de remise, basic.return sera renvoyé à l'expéditeur.
La source peut attendre la confirmation après l'envoi de chaque message, mais cela réduira considérablement ses performances. Au lieu de cela, les sources peuvent envoyer un flux constant de messages, fixant une limite au nombre de messages non acquittés. Lorsque la limite de messages intermédiaires est atteinte, l'envoi est suspendu jusqu'à ce que toutes les confirmations soient reçues.
Maintenant qu'il existe de nombreux messages en transit de l'expéditeur vers RabbitMQ, les confirmations sont regroupées à l'aide de l'indicateur multiple pour améliorer les performances. Tous les messages envoyés sur le canal se voient attribuer une valeur entière à augmentation monotone, le «numéro de séquence». La notification d'un message comprend le numéro de séquence du message correspondant. Et si en même temps la valeur est multiple = true, l'expéditeur doit suivre les numéros de séquence de ses messages afin de savoir quels messages ont été livrés avec succès et lesquels ne l'ont pas été. J'ai écrit un article détaillé sur ce sujet.
Grâce aux confirmations, nous évitons la perte de messages des manières suivantes:
- renvoyer des messages en cas de notification négative;
- stockage continu des messages quelque part en cas de notification négative ou de retour de base.
Les transactionsLes transactions sont rarement utilisées dans RabbitMQ pour les raisons suivantes:
- Garanties faibles. Si les messages sont acheminés vers plusieurs files d'attente ou ont une icône obligatoire, la continuité des transactions ne sera pas prise en charge;
- Faible productivité.
Honnêtement, je ne les ai jamais utilisés, ils ne donnent aucune garantie supplémentaire, à l'exception des confirmations à l'expéditeur, et ne font qu'accroître l'incertitude quant à la façon d'interpréter l'accusé de réception des messages résultant de l'achèvement des transactions.
Erreurs de communication / canalOutre les notifications de réception de messages, l'expéditeur doit tenir compte des défaillances des outils de communication et des courtiers. Ces deux facteurs entraînent la perte du canal de communication. Avec la perte de canaux, la possibilité de recevoir toute notification de réception de messages non encore reçue disparaît. Ici, l'expéditeur doit choisir entre le risque de perte de message et le risque de duplication.
L'échec du courtier peut se produire lorsque le message était dans la mémoire tampon du système d'exploitation ou prétraité, puis le message sera perdu. Ou, peut-être que le message était en file d'attente, mais le courtier de messages est mort avant d'envoyer une confirmation. Dans ce cas, le message sera remis avec succès.
De même, la défaillance des moyens de communication affecte la situation. Une défaillance s'est-elle produite lors de la transmission du message? Ou après la mise en file d'attente du message, mais avant de recevoir une notification positive?
L'expéditeur ne peut pas déterminer cela, il doit donc choisir l'une des options suivantes:
- Ne transmettez pas le message, créant un risque de perte;
- renvoyez le message et créez un risque de duplication.
Si de nombreux messages d'expéditeur sont en transit, le problème devient plus compliqué. La seule chose que l'expéditeur peut faire est de donner un indice aux destinataires en ajoutant un en-tête spécial au message, indiquant que le message est envoyé une deuxième fois. Les destinataires peuvent décider de vérifier la présence de ces en-têtes dans les messages et, s'ils sont trouvés, de vérifier en outre la présence de doublons dans les messages reçus (si une telle vérification n'a pas été effectuée auparavant).
Destinataires
Les destinataires disposent de deux options pour recevoir des notifications:
- pas de mode de notification;
- mode de notification manuelle.
Pas de mode de notificationIl s'agit d'un mode de notifications automatiques. Et il est dangereux. Tout d'abord, car lorsqu'un message pénètre dans votre application, il est supprimé de la file d'attente. Cela peut entraîner une perte de message si:
- la connexion a été interrompue avant la réception du message;
- le message est toujours dans le tampon interne et l'application a été désactivée;
- échec du traitement des messages.
De plus, nous perdons des mécanismes de contre-pression pour contrôler la qualité de la livraison des messages. En définissant le mode d'envoi des notifications manuellement, vous pouvez définir une prélecture (ou définir le niveau de services fournis, QoS) pour limiter le nombre unique de messages dont le système n'a pas encore confirmé la réception. Sans cela, RabbitMQ envoie des messages aussi rapidement que la connexion le permet, et cela peut être plus rapide que le récepteur ne peut les traiter. Par conséquent, les tampons sont pleins et des erreurs de mémoire se produisent.
Mode de notification manuelleLe destinataire doit envoyer manuellement une notification de réception de chaque message. Il peut définir une prélecture au cas où le nombre de messages serait supérieur à un et traiter plusieurs messages en même temps. Il peut décider d'envoyer une notification pour chaque message, ou il peut appliquer l'indicateur multiple et envoyer plusieurs notifications à la fois. Le regroupement des notifications améliore les performances.
Lorsque le destinataire ouvre le canal, les messages qui le traversent contiennent le paramètre Delivery Tag, dont la valeur est un nombre entier croissant de façon monotone. Il est inclus dans chaque notification de réception et est utilisé comme identifiant de message.
Les notifications peuvent être les suivantes:
- basic.ack. Après cela, RabbitMQ supprime le message de la file d'attente. Le drapeau multiple peut être appliqué ici.
- basic.nack. Le destinataire doit définir un indicateur pour indiquer à RabbitMQ s'il doit remettre le message en file d'attente. Lors de la réinitialisation, le message revient au début de la file d'attente. De là, il est à nouveau envoyé au destinataire (même au même destinataire). La notification basic.nack prend en charge l'indicateur multiple.
- basic.reject. Identique à basic.nack, mais ne prend pas en charge l'indicateur multiple.
Ainsi, sémantiquement basic.ack et basic.nack avec requeue = false sont identiques. Les deux opérateurs signifient la suppression d'un message de la file d'attente.
La question suivante est de savoir quand envoyer des notifications de réception. Si le message a été traité rapidement, vous souhaiterez peut-être envoyer une notification immédiatement après avoir terminé cette opération (réussie ou non). Mais si le message était dans la file d'attente RabbitMQ et que le traitement prend plusieurs minutes? L'envoi d'une notification après cela sera problématique, car si le canal se ferme, tous les messages auxquels il n'y a pas eu de notification seront renvoyés dans la file d'attente et l'envoi sera effectué une deuxième fois.
Erreur de connexion / message BrokerSi la connexion a été déconnectée ou qu'une erreur s'est produite dans le courtier, après quoi le canal cesse de fonctionner, tous les messages dont la réception n'a pas été confirmée sont à nouveau mis en file d'attente et transférés. C'est bon car cela empêche la perte de données, mais mauvais car cela provoque une duplication excessive.
Plus le destinataire a longtemps des messages dont il n'a pas confirmé la réception, plus le risque de transmission est élevé. Lorsqu'un message est renvoyé, RabbitMQ pour l'indicateur de transfert est défini sur true. Pour cette raison, le destinataire a au moins une indication que le message a peut-être déjà été traité.
IdempotenceSi l'idempotence est requise et garantit qu'aucun message n'est perdu, une vérification en double ou d'autres schémas idempotents doivent être intégrés. Si la vérification des messages en double est trop coûteuse, vous pouvez appliquer une stratégie dans laquelle l'expéditeur ajoute toujours un en-tête spécial aux messages renvoyés et le destinataire vérifie la présence d'un tel en-tête et d'un indicateur de renvoi dans les messages reçus.
Conclusion
RabbitMQ offre des garanties de messagerie fiables et à long terme, mais il existe de nombreuses situations où elles ne seront d'aucune aide.
Voici une liste de points à retenir:
- Vous devez utiliser la mise en miroir des files d'attente, des files d'attente fiables, des messages persistants, des accusés de réception pour l'expéditeur, un indicateur de confirmation et une notification forcée du destinataire si des garanties fiables sont requises dans la stratégie de «remise au moins ponctuelle».
- Si l'envoi est effectué dans le cadre de la stratégie de «livraison au moins unique», vous devrez peut-être ajouter un mécanisme de déduplication ou d'idempotence lors de la duplication des données envoyées.
- Si le problème de la perte de messages n'est pas aussi important que celui de la vitesse de livraison et de l'évolutivité élevée, alors pensez à des systèmes sans redondance, sans messages persistants et sans accusé de réception du côté source. Néanmoins, je préférerais laisser les notifications forcées du destinataire afin de contrôler le flux des messages reçus en modifiant les restrictions de prélecture. Dans ce cas, vous devrez envoyer des notifications par lots et utiliser l'indicateur «multiple».
Garanties de livraison à KafkaLes garanties de livraison sont fournies:
- durabilité des messages - les messages stockés dans un segment ne sont pas perdus;
- Notifications de messages - échange de signaux entre Kafka (et éventuellement le référentiel Apache Zookeeper) d'une part et la source / le récepteur d'autre part.
Deux mots sur le packaging des messagesL'une des différences entre RabbitMQ et Kafka est l'utilisation de packages pour la messagerie.
RabbitMQ fournit quelque chose de similaire à l'emballage grâce à:
- Suspendez l'envoi de tous les messages X jusqu'à ce que toutes les notifications soient reçues. RabbitMQ regroupe généralement les notifications en utilisant l'indicateur «multiple».
- «prefetch» «multiple».
. “multiple”. TCP.
Kafka . , . RabbitMQ, , , . , .
Kafka , , . , . RabbitMQ API , . RabbitMQ .
,Kafka - , , . . , , , , , .
Kafka (In Sync Replicas, ISR). . , , ( 10 ). , . - , .. . .
, Kafka , , , Kafka .
, Kafka, , :
- , . Acks=0.
- . Acks=1
- . Acks=All
, RabbitMQ. , , ( , ). , .
Kafka . :
- enable.idempotence “true”,
- max.in.flight.requests.per.connection 5 ,
- retries 1 ,
- acks “all”.
, acks=0/1 , .
, , . ZooKeeper Kafka.
(), , :
- . . . . , .
- , . “ ”. , ; , . , 10 , 4 , , , ;
- , . “ ”. , , , . , 10 , , 4 ;
- . , .
“ ” Kafka Streams, Java. Java . “ ”, , , . , , “ ” . , , () .
, Kafka Streams, , , “ ”. Kafka: . , . , , , ( ), .
Kafka “--”. . , , .
“ ”, , (, , ). “ ”, , . .
: “ ” ? . , , . . (Last Stable Offset, LSO) — ; “ ” .
Conclusions. , , . , Kafka , .
Pour résumer
- “ ” “ ”.
- .
- , . Kafka , .
- , , .
- .
- Kafka , “--”. .
- Kafka, - , ( ). RabbitMQ .
- Kafka peut augmenter les avantages de la mise en paquets en raison de ses capacités de distribution de paquets, et RabbitMQ n'a pas de mise en paquets en raison d'un modèle de réception passif qui n'empêche pas les conflits de destinataires.