Asynchronous data exchanges, découpler avec classe – partie 1

Déporter des traitements lourds, transférer des logs, gérer des pics de charges, architecture réactive… Il existe de nombreux cas d’utilisation du design pattern Asynchronous data exchanges qui permet de gérer la communication de message en mode asynchrone.

De nos jours, plein de solutions existent pour l’implémenter :

  • Utilisation de méthode intégrée aux langages :
    • Futures en Java
    • Actors et Futures en Scala
    • Delegate en .Net
  • Utilisation d’outil comme Netty
  • Utilisation de serveur de message ou MOM (Message Oriented Middleware)
  • Etc.

Dans cette série d’articles, nous allons regarder l’utilisation d’un MOM.

Pour cela nous allons voir très rapidement la théorie, les points d’attention et finir avec des cas d’utilisations dans le deuxième article.

Un peu de théorie

Un MOM nous permet d’envoyer et de recevoir des messages de manière asynchrone afin de découpler le producteur et le récepteur.

Modes de fonctionnement

Il existe trois modes de fonctionnement principaux (qui peuvent avoir des noms différents selon la norme).

Point à point/Direct exchange

Le producteur envoie un message dans le MOM qui l’envoie à un ou plusieurs consommateurs. Par contre un seul consommateur lit le message.

Point à point

 

Il est possible d’avoir plusieurs consommateurs en même temps.

Point à point

 

De même pour les producteurs.

Plusieurs producteurs peuvent écrire sur la même file de messages, mais il y a toujours un seul consommateur qui lit le message.

Point à point

 

Pour résumer, le message est transmis à 1 seule file de messages identifiée.

Nous utiliserons ce mode d’échange lorsque le message sera traité par un seul consommateur.

Publish-subscribe/Topic exchange

Les consommateurs s’abonnent à un topic. Dès qu’un message arrive dans ce topic, il est lu par tous les consommateurs présents à ce moment.

 

Publish/subscribe

 

Si un consommateur arrive après (et que la file de messages est non durable), il ne lira que les messages suivants.

Publish/subscribe

 

Nous utiliserons ce mode d’échange lorsque le message pourra être traité par plusieurs consommateurs. Par exemple pour implémenter des fils d’actualité, des notifications d’événements, etc.

Broadcast/Fanout exchange

Le producteur envoie un message dans le MOM qui l’envoie à toutes les files de messages qui y sont connectées à ce moment.

fanout

 

Les cas d’utilisation sont les mêmes que pour du broadcast classique.

Quelques normes/protocoles

De nombreuses normes/protocoles existent :

  • L’API JMS dans le monde Java
  • Le protocole AMQP
  • Le protocole MQTT adapté au monde de l’IoT
  • Le protocole STOMP plus léger et simple à implémenter
  • La solution Websphere MQ qui supporte JMS, mais a aussi son propre protocole et est adaptée au monde du Mainframe
  • La solution Apache Kafka adaptée pour les très grosses volumétries
  • Etc.

Quelques outils

Une fois la norme choisie, il nous reste à choisir le bon outil

Pour vous aider, voici une liste non exhaustive :

  • AWS SQS, service cloud d’Amazon AWS
  • Apache ActiveMQ, la référence
  • RabbitMQ, le petit frère d’ActiveMQ
  • Apache Kafka
  • Redis, utile si vous l’utilisez déjà pour du cache et que vous ne voulez pas ajouter une nouvelle brique
  • ZeroMQ, une API légère de messaging

Notez que :

  • Certains des outils listés supportent plusieurs protocoles
  • En fonction du cas d’utilisation (mode distribué, mode décentralisé, besoin d’une faible latence…), certains outils sont plus adaptés que d’autres

Points d’attention

Avant d’éventuellement choisir une des solutions (qui offre plein de bonne raison de les utiliser), il est préférable de se poser un peu et de réfléchir aux points suivants.

Plus de complexité pour la supervision et le débogage

Quel que soit l’outil utilisé, nous aurons accès à des mesures nous permettant de comprendre son fonctionnement.

Par contre le suivi de bout en bout d’une requête devient plus complexe et doit être pris en compte dès la phase de design.

Par exemple en utilisant le design pattern correlation id qui consiste à l’ajout d’un identifiant dans chaque requête pour suivre une transaction de bout en bout dans le SI.

correlationID

Ou en utilisant un APM (Application Performance Management) qui supporte la technologie utilisée.

Plus de complexité lors du développement

En fonction de l’outil choisi et de comment il est configuré, on devra faire attention à :

  • L’ordre des messages n’est pas garanti (solution possible avec ActiveMQ)
  • La possibilité d’avoir des messages en doublon
  • La présence ou non de transaction

Cela implique une autre difficulté : garder une cohérence à terme (qui désigne un modèle dans lequel la cohérence n’est garantie qu’à la « fin », la « fin » étant relative à votre cas d’utilisation).

indempotence

Une bonne configuration et utilisation de l’outil choisi associé à des messages idempotent pourra faire l’affaire dans de nombreux cas d’utilisation.

Bien maîtriser l’outil choisi

Les MOM permettent de faire du synchrone et de l’asynchrone.

Par exemple la fonction de failover d’ActiveMQ peut bloquer le producteur. Et donc il faut une bonne connaissance de l’outil pour ne pas tomber dans les pièges.

Attention à ce que le MOM ne devienne pas le SPOF du système

Un SPOF (Single Point Of Failure) est une brique du système dont une panne entraîne l’arrêt complet du reste. Et donc si on veut un système résilient, il faut aussi que notre MOM le soit.

Qui dit asynchrone, dit back-pressure

Lorsqu’on est en synchrone, le producteur est obligé d’attendre que le consommateur réalise son traitement.

Back pressure

Et donc lorsque la charge va augmenter et si le consommateur ne suit pas, la contention va être au niveau du producteur qui va empiler les tâches à réaliser.

Back pressure

Avec de l’asynchrone, c’est l’inverse, la contention va être au niveau du consommateur, car il va se retrouver avec toutes les tâches à réaliser, car le producteur n’aura pas fait barrage.

Back pressure

 

Et donc il est important de mettre en place une stratégie de back-pressure.

La stratégie de back-pressure consiste à gérer la réaction d’un système en présence de charge conséquente dans une chaîne de traitement. Sa maîtrise et son utilisation permettent d’augmenter la réactivité et la résilience des systèmes dans des conditions extrêmes.

Par exemple en utilisant la fonctionnalité de Producer Flow Control d’ActiveMQ.

Attention aux “poison messages” ou Dead Letter

Un “poison message” est un message qui va faire tomber le consommateur.

Par exemple avec un message mal formé ou avec un consommateur bogué qui ne prend pas en charge certains messages (taille, caractères non prévus…).

poison message

 

Le message n’ayant pas été consommé, lorsque le consommateur sera de nouveau disponible, sera traité à nouveau avec les mêmes conséquences (crash du consommateur).

poison message

 

Et ainsi de suite.

Encore une fois, une bonne configuration de la solution nous évitera ce type de problème.

Par exemple en utilisant le mécanisme de Dead Letter Exchanges de RabbitMQ ou de dead-letter queue d’ActiveMQ.

Pas adaptée à tous les cas d’utilisation, la taille compte

La taille du message

Pour des messages de très grande taille, il est préférable d’utiliser une autre solution (par exemple un ETL ou un batch).

Le nombre de messages dans la file de messages

En plus de la taille des messages il faut être prudent avec leur nombre dans la file de messages. Car si ce nombre augmente trop, il y a un risque de consommation élevé de la mémoire et donc d’un crash.

Pour éviter cela, il y a plusieurs solutions :

  • Augmenter le nombre de consommateurs
  • Bien paramétrer le TTL (Time To Live) des messages
  • etc.

Persister les messages sur disque/base de données, au prix d’une perte de performance, peut éviter de perdre des messages en cas de crash (sauf bien sûr si le disque/base de données crash aussi).

Quelle que soit la solution retenue, une supervision de cette métrique sera toujours utile.

Pour cela il existe plusieurs solutions :

  • Utilisation de la console de supervision intégrée au MOM (par exemple pour RabbitMQ)
  • Utilisation d’outil de supervision supportant le MOM choisi (par exemple hawtio)
  • Utilisation des API fournis par le MOM

Conclusion

Une des solutions pour implémenter le design pattern Asynchronous data exchanges est d’utiliser un MOM.

Nous avons vu le fonctionnement d’un MOM (point à point, publish/subscribe) et quelques outils.

Petit rappel l’ajout d’un MOM et d’appel asynchrone dans une application n’est pas anodin et donc il faut que cela réponde à un vrai besoin.

Cas d’utilisation que nous verrons dans le prochain article.

 

Pour aller plus loin, notre nouveau livre blanc sur le sujet vient de sortir :

TELECHARGER LE LIVRE BLANC