Conception et dénomination des files d'attente

Quelques règles sur la façon d'organiser les points, les files d'attente et comment les nommer correctement pour le rendre pratique.

| exchange | type | binding_queue | binding_key | |-------------------------------------------------------| | user.write | topic | user.created.app2 | user.created | 

Les concepts
AMQP (Advanced Message Queuing Protocol) est un protocole ouvert pour transférer des messages entre les composants du système.
Fournisseur (éditeurs / producteur) - un programme qui envoie des messages.
Abonné (consommateur) - un programme qui accepte les messages. En règle générale, l'abonné est dans un état d'attente pour les messages.
File d'attente - Une file d'attente de messages.
Point d'échange (Exchange) - Le point d'échange initial de la file d'attente, qui est engagé dans le routage.

Règle 1


Chaque file d'attente ne doit représenter qu'un seul type de travail. Ne mélangez pas différents types de messages dans la même file d'attente. Et lorsque cette règle est respectée, on peut clairement nommer la file d'attente des tâches qui leur est présentée.

Règle 2


Évitez de soumettre à nouveau les messages à la file d'attente. Si vous constatez que votre abonné essaie de renvoyer des messages à d'autres files d'attente sans traitement réel, quelque chose n'est probablement pas conçu correctement. Le routage est la responsabilité des points d'échange, pas des files d'attente.

Règle 3


Les fournisseurs n'ont besoin de rien savoir des files d'attente. L'une des principales idées d'AMQP est la répartition des responsabilités entre les points et les files d'attente, afin que les fournisseurs n'aient pas besoin de veiller à ce que le message parvienne à l'abonné.

Exemples et solutions


Supposons que vous souhaitiez concevoir des points et des files d'attente pour des événements d'enregistrement liés «personnalisés». Les événements d'enregistrement seront déclenchés dans une ou plusieurs applications, et ces messages seront utilisés par d'autres applications.

 | object | event | |------------------| | user | created | | user | updated | | user | deleted | 

La première question qui est généralement posée est les différents événements d'un objet (l'objet "utilisateur" dans cet exemple), un point d'échange devrait-il être utilisé pour publier les trois événements, ou 3 points distincts devraient-ils être utilisés pour chaque événement? Ou, en bref, un point d'échange, ou plusieurs?

Avant de répondre à cette question, je voudrais poser une autre question: avons-nous vraiment besoin d'un point distinct pour ce cas? Que se passe-t-il si nous abstenons les 3 types d'événements comme un événement «d'écriture» dont les sous-types sont «créés», «mis à jour» et «supprimés»?

 | object | event | sub-type | |-----------------------------| | user | write | created | | user | write | updated | | user | write | deleted | 

Solution 1


La solution la plus simple consiste à créer la file d'attente «user.write» et à publier tous les messages des événements d'enregistrement de l'utilisateur dans cette file d'attente via le point d'échange global.

Décision 2


La solution la plus simple ne peut pas fonctionner lorsqu'une deuxième application (ayant une logique de traitement différente) souhaite s'abonner à tous les messages publiés dans la file d'attente. Lorsque plusieurs applications sont signées, nous avons au moins besoin d'un point de type «fanout» avec des liaisons vers plusieurs files d'attente. Ainsi, les messages sont envoyés au point, et il duplique les messages dans chaque file d'attente. Chaque file d'attente représente un travail de traitement pour chaque application.

 | queue | subscriber | |-------------------------------| | user.write.app1 | app1 | | user.write.app2 | app2 | | exchange | type | binding_queue | |---------------------------------------| | user.write | fanout | user.write.app1 | | user.write | fanout | user.write.app2 | 

La deuxième solution fonctionne bien si chaque abonné veut vraiment gérer tous les sous-types d'événements "user.write". Par exemple, si l'application de l'abonné est conçue pour simplement stocker un journal des transactions.

En revanche, ce n'est pas très bien lorsque certains abonnés sont en dehors de votre organisation et que vous souhaitez les informer uniquement de certains événements spécifiques, par exemple, app2 devrait recevoir un message sur la création d'utilisateurs et ne devrait pas être au courant des événements de mise à jour et de suppression.

Décision 3


Pour résoudre le problème ci-dessus, nous devons extraire l'événement «user.created» du type «user.write». Un point d'échange avec un type de sujet peut nous aider. Lors de la publication des messages, nous utiliserons user.created / user.updated / user.deleted comme clés de routage à un moment donné, afin que nous puissions mettre la clé de communication "user. *" Dans la file d'attente "user.write.app1" et la clé de communication "user.created" dans la file d'attente "user.created.app2".

 | queue | subscriber | |---------------------------------| | user.write.app1 | app1 | | user.created.app2 | app2 | | exchange | type | binding_queue | binding_key | |-------------------------------------------------------| | user.write | topic | user.write.app1 | user.* | | user.write | topic | user.created.app2 | user.created | 

Décision 4


Le type de sujet de sujet du point d'échange est plus flexible au cas où il y aurait potentiellement plus de types d'événements. Mais si vous connaissez clairement le nombre exact d'événements, vous pouvez également utiliser le type «direct» pour améliorer les performances.

 | queue | subscriber | |---------------------------------| | user.write.app1 | app1 | | user.created.app2 | app2 | | exchange | type | binding_queue | binding_key | |--------------------------------------------------------| | user.write | direct | user.write.app1 | user.created | | user.write | direct | user.write.app1 | user.updated | | user.write | direct | user.write.app1 | user.deleted | | user.write | direct | user.created.app2 | user.created | 

Nous revenons à la question "un point, ou beaucoup?". Tant que toutes les solutions n'utilisent qu'un seul point, cela fonctionne bien, rien de mal. Dans quelles situations peut-on avoir besoin de plusieurs points?

Décision 5


Regardons un exemple où, en plus des événements créés, mis à jour et supprimés décrits ci-dessus, nous avons un autre groupe d'événements: entrée et sortie - un groupe d'événements qui décrivent le «comportement de l'utilisateur» plutôt que «l'enregistrement de données». Pour différents groupes d'événements, des stratégies de routage complètement différentes et un accord sur les clés et les noms des files d'attente peuvent être nécessaires, et pour cela, des points d'échange distincts sont nécessaires.

 | queue | subscriber | |----------------------------------| | user.write.app1 | app1 | | user.created.app2 | app2 | | user.behavior.app3 | app3 | | exchange | type | binding_queue | binding_key | |--------------------------------------------------------------| | user.write | topic | user.write.app1 | user.* | | user.write | topic | user.created.app2 | user.created | | user.behavior | topic | user.behavior.app3 | user.* | 

Traduction gratuite de l'article RabbitMQ Exchange and Queue Design Trade-off .

Source: https://habr.com/ru/post/fr443722/


All Articles