angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Du code monolithique aux services avec RabbitMQ et Pika

Utilisez les exemples de la documentation Pika pour créer vos propres classes d'éditeur et de consommateur RabbitMQ .

8 janvier 2023 Mise à jour 9 janvier 2023
Dans RabbitMQ
post main image
https://www.pexels.com/nl-nl/@jeshoots-com-147458

Ce post concerne l'utilisation de RabbitMQ dans votre application Python . Si vous utilisez déjà Rabbit, vous ne trouverez probablement rien d'utile dans cet article. Pourquoi un article sur RabbitMQ ? Parce que j'ai une application qui l'utilise depuis un an, et j'ai pensé à partager mes expériences.

Dans ce billet, nous transformons une application monolithique en services découplés par des files d'attente. De plus, je parlerai de services plutôt que de microservices, vous décidez de la taille de votre service. Il n'y a pas d'échantillons de code dans cet article.

Exemple d'application monolithique : Formulaire de contact

L'exemple que nous allons utiliser est le processus de formulaire de contact. Un user de notre site Web entre les données du formulaire de contact. Celles-ci sont traitées, un email est préparé et l'email est envoyé à un serveur externe SMTP.

        +-----------------+    smtp
user    | - process form  |  server
------->| - prepare email |------->
        | - send email    |
        +-----------------+

Pourquoi vouloir diviser votre code en services ?

Il existe un certain nombre d'avantages et d'inconvénients à diviser votre code :

Avantages (idéalement) :

  • Une séparation plus stricte entre les principaux services (fonctions) de votre code.
  • Les services peuvent être arrêtés et redémarrés sans perte de données.
  • Performance. En se découplant, un service devient disponible plus rapidement.
  • Asynchrone. Un service attendra qu'un autre service devienne disponible, sans avoir d'impact sur les autres services.

Inconvénients :

  • Complexité accrue.
  • Augmentation du temps de développement.
  • Plus grande difficulté à tester.

Résumé des avantages : un système plus rapide et plus flexible. Le résumé des inconvénients est le suivant : (beaucoup) plus de travail.

Il y a beaucoup d'articles sur Internet qui parlent de l'"enfer des microservices", mais il y a autant d'articles qui parlent de l'"enfer monolithique". Je vous suggère de faire quelques recherches vous-même. Voici un bon article : You Don't Need Microservices", voir les liens ci-dessous. Lisez également les commentaires.

Les services après la scission

Voici les services, après avoir divisé notre code.

        +---------+     +---------+     +---------+    smtp
user    | process |     | prepare |     | send    |  server
------->| form    |---->| email   |---->| email   |-------> 
        |         |     |         |     |         |
        +---------+     +---------+     +---------+

Rien de très spécial, c'est juste un monolithe bien structuré.

RabbitMQ et Pika

Pour découpler les services, nous utilisons des files d'attente. RabbitMQ est un courtier en messages et prend en charge les files d'attente de messages, les protocoles de messagerie multiples, etc. Pour communiquer avec RabbitMQ dans Python , nous utilisons le package Python Pika. Pika est une implémentation pure de Python du protocole AMQP 0-9-1 .

Les files d'attente dans RabbitMQ sont FIFO, premier entré-premier sorti. Avec RabbitMQ, vous pouvez définir des échanges et des files d'attente. Typiquement, vous définissez un échange et vous liez ensuite les files d'attente à cet échange.

Un éditeur est utilisé pour placer des éléments dans la file d'attente. Un consommateur est utilisé pour récupérer les éléments de la file d'attente. Vous trouverez des exemples d'éditeurs et de consommateurs dans la documentation Pika .

        +-----------+     +-------------+     +-----------+    
        |           |     | RabbitMQ    |     |           |  
------->| publisher |---->| - exchange  |---->| consumer  |-------> 
        |           |     | - queue     |     |           |
        +-----------+     +-------------+     +-----------+

Les éditeurs et les consommateurs peuvent être synchrones ou asynchrones.

Dans de nombreux cas, nous aurons un :

  • Un éditeur synchrone. Cela signifie que nous envoyons un élément dans la file d'attente RabbitMQ et que nous attendons la confirmation que l'élément a été placé dans la file d'attente, avant de continuer à faire autre chose.
  • Consommateur asynchrone. Cela signifie que notre fonction de réception est appelée par RabbitMQ dès qu'un élément est disponible dans la file d'attente.

RabbitMQ et les accusés de réception

RabbitMQ dispose de nombreuses options, la plus importante à comprendre étant les accusés de réception. Il existe deux types d'accusés de réception.

  • L'accusé de réception positif
  • Accusé de réception négatif

Accusés de réception de l'éditeur (synchrone)

Lorsque l'éditeur (notre code) envoie un élément à RabbitMQ, RabbitMQ répond en envoyant un :

  • Un accusé de réception positif
    L'article a été reçu par RabbitMQ.
  • Accusé de réception négatif
    Un problème est survenu. Peut-être que l'échange ou la file d'attente n'existait pas, la clé de routage était erronée.

Accusés de réception du consommateur (asynchrone)

Notre consommateur est appelé par RabbitMQ avec le prochain élément de la file d'attente. Après avoir traité l'élément, le consommateur (notre code) répond en envoyant un :

  • Accusé de réception positif
    L'élément a été reçu et traité, je suis prêt pour l'élément suivant.
  • Accusé de réception négatif
    L'élément a été reçu mais quelque chose s'est mal passé pendant le traitement. RabbitMQ ne supprimera PAS l'élément de la file d'attente et réessayera.

Découplage des services avec les files d'attente

Pour découpler nos services, nous plaçons une file d'attente entre le service 'process form' et le service 'prepare email' :

         publisher                     consumer
        +---------+     +-------+     +---------+    
user    | process |     |       |     | prepare |  
------->| form    |---->| queue |---->| email   |---->
        |         |     |       |     |         |
        +---------+     +-------+     +---------+

Le service 'process form' joue le rôle d'éditeur. Il contient le code permettant d'envoyer des éléments dans la file d'attente. Le service 'prepare email' joue le rôle de consommateur. Il contient du code pour recevoir des éléments dans la file d'attente.

La file d'attente de sortie est une "partie" de l'éditeur.

Supposons maintenant que les deux services sont exécutés sur deux serveurs. Comme nous voulons que nos services puissent être arrêtés et redémarrés sans perte de données, il est important de comprendre où nous plaçons la file d'attente entre les services. Sur le serveur de l'éditeur, ou sur le serveur du consommateur.

La réponse est bien sûr que la file d'attente doit être sur le même serveur que le service 'process form' . Ainsi, lorsque le serveur du service 'prepare email' est en panne, le service 'process form' peut continuer à fonctionner. Les User peuvent continuer à envoyer des formulaires de contact, les données sont temporairement stockées dans la file d'attente. Une fois que le service 'prepare email' est à nouveau opérationnel, il traite toutes les données en attente dans la file d'attente.

                  server 1                      server 2
         - - - - - - - - - - -  - -        - - - - - - - - - - 
                                    |     | 
         publisher                             consumer
        +---------+     +-------+   |     |   +---------+    
user    | process |     |       |             | prepare |  
------->| form    |---->| queue |------------>| email   |---->
        |         |     |       |             |         |
        +---------+     +-------+   |     |   +---------+

                                    |     | 
         - - - - - - - - - - -  - -        - - - - - - - - - - 

Dans l'exemple ci-dessus, nous avons utilisé des serveurs. Mais la même chose s'applique lorsque vous avez tout sur un seul serveur, mais que vous avez deux projets Docker-Compose différents, un pour le service 'process form' et un pour le service 'prepare email' . Dans ce cas, nous ajoutons RabbitMQ au projet 'process form' Docker-Compose.

Un consommateur et un éditeur à la fois

Il est parfaitement logique qu'un service soit à la fois consommateur et éditeur. Dans notre exemple, le service 'prepare email' et le service 'send email' agissent tous deux en tant que consommateur et éditeur.

                                       consumer                      consumer
                                          +                             +
         publisher                     publisher                     publisher
        +---------+     +-------+     +---------+     +-------+     +---------+    
 user   | process |     |       |     | prepare |     |       |     | send    |  
------->| form    |---->| queue |---->| email   |---->| queue |---->| email   |---->
        |         |     |       |     |         |     |       |     |         |
        +---------+     +-------+     +---------+     +-------+     +---------+

Le fonctionnement normal du service 'prepare email' est le suivant :

  1. Le consommateur est appelé avec le prochain élément de la file d'attente d'entrée.
  2. L'élément est traité.
  3. Les nouvelles données sont publiées dans la file d'attente de sortie.
  4. Un accusé de réception positif de publication est reçu.
  5. Le consommateur envoie un accusé de réception positif à la file d'attente d'entrée.
  6. RabbitMQ supprime l'élément de la file d'attente d'entrée.
  7. RabbitMQ appelle le consommateur avec le prochain élément de la file d'attente d'entrée, une fois disponible.

Si quelque chose ne va pas dans le processus du consommateur, par exemple l'échec de la publication dans la file d'attente de sortie, le consommateur peut envoyer un accusé de réception négatif à RabbitMQ. Dans ce cas, RabbitMQ ne supprimera pas l'élément de la file d'attente d'entrée, mais réessayera le même élément.

Démarrage avec RabbitMQ

La prise en main de RabbitMQ est difficile. Les développeurs d'applications veulent utiliser des recettes, et non lire une quantité infinie de documents. J'ai commencé par installer Docker RabbitMQ avec le Management Plugin. Le Management Plugin vous permet de créer des échanges et des files d'attente, de voir le nombre d'éléments dans les files d'attente et bien plus encore.

J'ai ensuite essayé les exemples de la documentation Pika . Une fois que vous les aurez compris, vous pourrez utiliser ce code pour construire vos propres classes d'éditeur et de consommateur, à utiliser dans votre application.

Notes aléatoires

La publication asynchrone est complexe, je ne l'ai pas utilisée jusqu'à présent. La publication synchrone avec RabbitMQ n'est pas très rapide, surtout si vous voulez que les données persistent. Et vous voulez probablement que les données de la file d'attente persistent, car avec cette option, les données ne sont pas perdues lorsque vous arrêtez RabbitMQ ou en cas de panne du système.

Vous pouvez accélérer la publication synchrone en envoyant une liste d'éléments dans la file d'attente au lieu d'un seul élément. Mais cela dépend de votre application.

Je n'utilise pas une seule instance RabbitMQ tolérante aux pannes, mais je fais tourner plusieurs instances RabbitMQ avec les services. J'utilise RabbitMQ avec plusieurs projets Docker-Compose, c'est juste un des services.

RabbitMQ est gourmand en mémoire. Mon application n'est pas très complexe ou exigeante, mais une instance de RabbitMQ utilise environ 200 Mo selon Docker Stats.

Certaines personnes sur Internet se plaignent de l'utilisation élevée de CPU . J'ai remarqué quelques pics d'utilisation de CPU . RabbitMQ stocke beaucoup de données, qui semblent ne pas disparaître après un crash/kill. Après avoir supprimé cette fonction, les pics d'utilisation de CPU ont disparu.

J'utilise également le plugin Shovel pour envoyer des données d'une instance RabbitMQ à une autre, en utilisant Docker Network. Cela fonctionne également très bien.

Vous pouvez utiliser votre application pour configurer les échanges et les files d'attente de RabbitMQ . Je ne le fais pas, mais je configure les échanges et les files d'attente à l'aide du Management Plugin, puis j'exporte le nouveau fichier de configuration et le copie dans le fichier de configuration de RabbitMQ .

J'utilise Dockerized RabbitMQ depuis plus d'un an maintenant et son fonctionnement est très fiable. Je n'ai jamais eu à redémarrer parce que quelque chose n'allait pas.

Résumé

La division de votre système monolithique en services avec RabbitMQ en vaut-elle la peine ? Oui, mais, à moins que vous ne soyez une grande entreprise, n'exagérez pas car cela ajoute aussi beaucoup de complexité. Comme toujours, cette douleur s'atténue lorsque vous commencez à comprendre davantage RabbitMQ. Enfin, RabbitMQ devrait être dans la boîte à outils de tout développeur.

Liens / crédits

Pika
https://pika.readthedocs.io

RabbitMQ
https://www.rabbitmq.com

You Don’t Need Microservices
https://itnext.io/you-dont-need-microservices-2ad8508b9e27

En savoir plus...

Pika RabbitMQ

Laissez un commentaire

Commentez anonymement ou connectez-vous pour commenter.

Commentaires

Laissez une réponse

Répondez de manière anonyme ou connectez-vous pour répondre.