Vous êtes sur la page 1sur 12

RabbitMQ, des bases à la maîtrise (Partie 1)

RabbitMQ est un message broker très complet et robuste, c’est pourquoi le comprendre et
l’utiliser est assez simple. en revanche, le maîtriser l’est un peu moins… C’est pourquoi je
vous propose cette série de deux articles.

Bref, pour commencer, avant de manger du pâté de lapin il va falloir bouffer des carottes !

Introduction
RabbitMQ a de nombreux points forts, ce qui en fait une solution utilisable sur tous
types/tailles de projet.

En voici quelques-uns :

• Utilise AMQP (courante: 0.9.1)


• Développé en Erlang ce qui en fait un logiciel très robuste
• Système de clustering pour la haute disponibilité et la scalabilité
• Un système de plugins qui permet d’apporter d’autre fonctionnalités (management,
ldap, shovel, mqtt, stomp, tracing, AMQP 1.0)
• Les vhost permettent de cloisonner des environnements (mutualiser le serveur, env
dev/preprod/prod)
• Quality Of Service (QOS) permet de prioriser les messages

AMQP
Ok, donc on va commencer par semer des carottes

Afin de pouvoir utiliser efficacement RabbitMQ il faut comprendre le fonctionnement du


protocol AMQP.

Le Broker

RabbitMQ est un message broker, son rôle est de transporter et router les messages depuis les
publishers vers les consumers. Le broker utilise les exchanges et bindings pour savoir si il
doit délivrer, ou non, le message dans la queue.

Voici le fonctionnement global du broker :

Le publisher va envoyer un message dans un exchange qui va, en fonction du binding,


router le message vers la ou les queues. Ensuite un consumer va consommer les messages.
Nous allons donc détailler les différents éléments qui composent le broker.

Le message

Le message est comme une requête HTTP, il contient des attributs ainsi qu’un payload.
Parmi les attributs du protocol vous pouvez y ajouter des headers depuis votre publisher.

Liste des properties du protocol content_type, content_encoding, priority, correlation_id,


reply_to, expiration, message_id, timestamp, type, user_id, app_id, cluster_id

Les headers seront disponibles dans attributes[headers].

L’attribut routing_key, bien qu’optionnel, n’en est pas moins très utile dans le protocol.

Les Bindings

Les bindings, ce sont les règles que les exchanges utilisent pour déterminer à quelle queue il
faut délivrer le message. Les différentes configurations peuvent utiliser la routing key
(direct/topic exchanges) ainsi que les headers(header exchanges). Dans le cas des exchanges
fanout, les queues n’ont qu’à être bindées pour recevoir le message.

Nous allons détailler leurs utilisations.

Les Exchanges

Un exchange est un routeur de message. Il existe différents types de routages définis par le
type d’exchange.

Vous publiez dans un exchange. Vous ne consommez pas un exchange !

Important à savoir : l’exchange amq.default est l’exchange par défaut de rabbit. Vous ne
pouvez ni le supprimer ni vous binder dessus.
Cet exchange est auto bindé avec toutes les queues avec une routing key égale au nom de
la queue.

L’exchange type fanout

L’exchange fanout est le plus simple. En effet il délivre le message à toutes les queues
bindées.

L’exchange type direct

L’exchange direct n’autorise que le binding utilisant strictement la routing key.


Si la routing_key du message est strictement égale à la routing_key spécifiée dans le
binding alors le message sera délivré à la queue.

binding.routing_key == message.routing_key

L’exchange type topic

L’exchange topic délivre le message si routing_key du message matche le pattern défini


dans le binding.

Une routing key est composé de plusieurs segments séparés par des .. Il y a également 2
caractères utilisés dans le matching.

* n’importe quelle valeur de segment


# n’importe quelle valeur de segment une ou plusieurs fois

Par exemple pour la routing key foo.bar.baz

• foo.*.baz match
• foo.*.* match
• foo.# match
• foo.#.baz match
• *.*.baz match
• #.baz match
• #.bar.baz match
• # match
• foo.* non trouvé

match(binding.routing_key, message.routing_key)

L’exchange type headers

L’exchange headers délivre le message si les headers du binding matchent les headers du
message.

L’option x-match dans le binding permet de définir si un seul header ou tous doivent
matcher.

x-match = any

Avec le x-match = any le message sera délivré si un seul des headers du binding correspond
à un header du message.

binding.headers[attrName1] == message.headers[attrName1] OU binding.headers[attrName2]


== message.headers[attrName2]
Le message sera délivré si le header attrName1 (configuré au moment du binding) est égal
au header attrName1 du message

OU

si le header attrName2 est égal au header attrName2 du message.

x-match = all

Avec le x-match = all le message sera délivré si tous les headers du binding correspondent
aux headers du message.

binding.headers[attrName1] == message.headers[attrName1] ET binding.headers[attrName2]


== message.headers[attrName2]

Ici le message sera délivré seulement si les headers attrName1 ET attrName2 (du binding)
sont égaux aux headers attrName1 et attrName2 du message.

Les Queues

Une queue est l’endroit où sont stockés les messages. Il existe des options de configuration
afin de modifier leurs comportements.

Quelques options :

• Durable, (stockée sur disque) la queue survivra au redémarrage du broker. Attention


seuls les messages persistants survivront au redémarrage.
• Exclusive, sera utilisable sur une seule connexion et sera supprimée à la clôture de
celle-ci.
• Auto-delete, la queue sera supprimée quand toutes les connections sont fermées (après
au moins une connexion).

Vous publiez dans un exchange. Vous ne consommez pas un exchange ! (quand vous croyez
publier dans une queue en réalité le message est publié dans l’exchange amq.default avec la
routing key = queue name)

Consumer
Le rôle du consumer est d’exécuter un traitement après avoir récupéré un ou plusieurs
messages.

Pour ce faire il va réserver (prefetching) un ou plusieurs messages depuis la queue, avant


d’exécuter un traitement. Généralement si le traitement s’est correctement déroulé le
consumer va acquitter le message avec succès (basic.ack). En cas d’erreur le consumer peut
également acquitter négativement le message (basic.nack). Si le message n’est pas acquitté, il
restera à sa place dans la queue et sera re fetch un peu plus tard.

Vous pouvez maintenant consulter la partie 2 (maîtrise), dans laquelle nous verrons comment
attraper les lapins, et comment préparer le pâté.
Liens utiles
http://www.rabbitmq.com/documentation.html

RabbitMQ des bases à la maîtrise (Partie 2)

Introduction
Après avoir vu les bases dans RabbitMQ : Les bases (Partie 1), nous allons pousser un peu
plus loin l’utilisation de RabbitMQ.

Plugins
Les plugins sont comme des engrais pour votre champ de carottes.

Je vous invite à consulter la page des plugins ainsi que le Github afin de voir les plugins
officiels disponibles.

D’autre part je vous conseille fortement d’activer au minimum les plugins suivants :

• rabbitmq_management ce plugin ajoute une interface web très pratique pour


configurer RabbitMQ.
• rabbitmq_tracing ce plugin (dans l’onglet Admin > Tracing) vous permet de tracer
(debug) les messages.

Authentification / Autorisation
Dans tout système d’informations, l’utilisation de permissions, par utilisateur/groupe, est une
notion très importante. Elle permet d’organiser et maîtriser l’utilisation et l’accès au service.

RabbitMQ embarque un système interne d’authentification/autorisation mais une fois de plus


il existe différents plugins d’auth.

ℹ️ Avec le plugin rabbitmq-auth-backend-http vous pouvez même déléguer cette partie à une
API HTTP (Les utilisateurs de votre plateforme sont connectés à RabbitMQ ! ). Voici une
implémentation en PHP qui utilise le composant security de Symfony.

Vous avez même la possibilité de configurer plusieurs systèmes d’auth en cascade.

auth_backends.my_auth_1 = internal
auth_backends.my_auth_2 = http
...
Ici les valeurs my_auth_1 et my_auth_2 sont arbitraires et peuvent prendre n’importe quelle
valeur.

Utilisateur

Un utilisateur (username, password facultatif) est utilisé pour se connecter à RabbitMQ afin
de publier et consommer les messages.

Le plugin rabbitmq_management ajoute une notion de tags (administrator, monitoring,


policymaker, management, impersonator) afin de limiter l’accès aux différentes parties de
l’interface.

Une fois votre utilisateur créé, il faudra lui ajouter des permissions sur chaque vhost auxquels
il aura accès.

Sur le backend d’auth par défaut (rabbit_auth_backend_internal), les permissions sont


séparées en 3 groupes :

• Configure regexp
• Write regexp
• Read regexp

Pour une utilisation plus simple des regexp je vous conseille d’avoir une vraie stratégie de
nommage des exchanges/queues avec des préfixes/segments/suffixes. D’une part vous
pourrez plus facilement identifier qui a créé les ressources mais aussi qui les consomme.

Je vous laisse consulter le tableau de répartition des actions par ressource

Maintenant vous pouvez facilement identifier vos petits lapins.

Policies
Les policies sont des règles de configurations qui s’appliquent aux exchanges et aux queues
(dont le nom matche une regexp) afin de diminuer la redondance de configuration mais aussi
et surtout de pouvoir changer une policy sans avoir à détruire et recréer la ressource
(exchange/queue). Certaines options de configuration d’une policy sont spécifiques aux
exchanges et d’autres aux queues.

Les Policies peuvent être utilisées pour configurer :

• federation plugin
• mirrored queues
• alternate exchanges
• dead lettering
• per-queue TTLs
• maximum queue length.

Attention, l’utilisation de policies peut devenir rapidement complexe.

Retry (Dead letter)


Les retries sont un autre sujet très important de RabbitMQ ! Quand le message consumer
rencontre une erreur durant le traitement d’un message il peut être intéressant dans certains
cas de réessayer le traitement du message. Les différentes solutions sont :

• Ne pas ACK ou NACK le message (Retry infini instantané bloquant)

Le message va garder sa place dans la queue et le consumer va de nouveau récupérer ce


message au prochain get. Je déconseille très fortement cette approche ! Car le consumer
va toujours récupérer le même message jusqu’au succès du traitement, qui pourrait ne jamais
se produire et créer une boucle infinie. De plus le message en erreur bloque le dépilement des
messages suivants.
• NACK le message avec une queue configurée avec DLX = “” (default exchange
amq.default) et DLK = {QUEUENAME} (Retry infini instantané non bloquant)

Le message va être remis en début de queue. Je déconseille également cette approche !


Cette fois-ci, le message ne va pas bloquer le dépilement des autres messages de la queue,
mais il peut quand même créer une boucle infinie si il n’y a qu’un message dans la queue.

• ACK le message après avoir publié un clone du message depuis le consumer.


(Solution la plus dynamique -> retry retardé variable non bloquant)

ℹ️ Avec cette solution on peut facilement gérer des “délais avant retry” variables. Premier retry
à 5 secondes, deuxième à 10 secondes, etc… Je garde une réserve sur cette pratique car
elle fonctionne mais positionne la responsabilité du retry du côté applicatif.

• NACK le message avec un délai avant de retry le message (Le “délai avant retry” est
fixe -> retry retardé fix non bloquant)

Le message va être remis en début de queue après avoir été mis en attente pendant un
temps défini.

C’est cette dernière solution que nous allons détailler.

Pour mettre en place cette solution nous allons devoir créer un exchange et une queue
d’attente.

Créer un exchange qui va router les messages dans la queue d’attente waiting_5 type
fanout. Créer une queue d’attente waiting_5 avec x-dead-letter-exchange: "" et x-
message-ttl: 5000. Puis binder cette queue sur l’exchange waiting_5.

le x-dead-letter-exchange doit être configuré avec une chaîne vide (amq.default).

Configurez ensuite votre queue queue1 avec x-dead-letter-exchange: "waiting_5" x-


dead-letter-routing-key: queue1.

le x-dead-letter-routing-key doit être configuré avec le nom de la queue.


Avec cette configuration, quand le consumer NACK le message, RabbitMQ redirige le
message dans l’exchange waiting_5 (fanout) qui va donc router ce message dans la queue
waiting_5. La queue waiting_5 va attendre 5 secondes avant d’expired le message, il va
donc arriver dans l’exchange amq.default avec comme routing key queue1 et donc être
routé dans la queue queue1.

ℹ️ À noter que le retry est infini. Ce qui peu également créer des poison messages.

Poison message
Un poison message c’est un message que le consumer rejettera (NACK) à chaque fois qu’il
va le consommer. Afin de traiter les poisons messages il faut que le consumer regarde dans les
properties du message afin de vérifier que le nombre de tentatives n’a pas été atteint.
Si le nombre de retry a été atteint il faudra loguer une erreur et ACK le message.

Production
Consultez la documentation sur les recommandations pour les serveurs de production.

Liens utiles
https://www.rabbitmq.com/admin-guide.html

Vous aimerez peut-être aussi