Microservice Communication : Tutoriel d'intégration Spring avec Redis
Publié: 2022-03-11L'architecture de microservice est une approche très populaire dans la conception et la mise en œuvre d'applications Web hautement évolutives. La communication au sein d'une application monolithique entre composants est généralement basée sur des appels de méthode ou de fonction au sein d'un même processus. Une application basée sur des microservices, en revanche, est un système distribué fonctionnant sur plusieurs machines.
La communication entre ces microservices est importante pour avoir un système stable et évolutif. Il existe plusieurs façons de procéder. La communication basée sur les messages est un moyen de le faire de manière fiable.
Lors de l'utilisation de la messagerie, les composants interagissent les uns avec les autres en échangeant des messages de manière asynchrone. Les messages sont échangés via des canaux.
Lorsque le service A veut communiquer avec le service B, au lieu de l'envoyer directement, A l'envoie sur un canal spécifique. Lorsque le service B veut lire le message, il récupère le message à partir d'un canal de message particulier.
Dans ce didacticiel Spring Integration, vous apprendrez à implémenter la messagerie dans une application Spring à l'aide de Redis. Vous serez guidé à travers un exemple d'application où un service envoie des événements dans la file d'attente et un autre service traite ces événements un par un.
Intégration de printemps
Le projet Spring Integration étend le framework Spring pour prendre en charge la messagerie entre ou au sein des applications basées sur Spring. Les composants sont câblés ensemble via le paradigme de messagerie. Les composants individuels peuvent ne pas être conscients des autres composants de l'application.
Spring Integration fournit une large sélection de mécanismes pour communiquer avec des systèmes externes. Les adaptateurs de canal sont l'un de ces mécanismes utilisés pour l'intégration unidirectionnelle (envoi ou réception). Et les passerelles sont utilisées pour les scénarios de demande/réponse (entrants ou sortants).
Apache Camel est une alternative largement utilisée. L'intégration de Spring est généralement préférée dans les services basés sur Spring existants, car elle fait partie de l'écosystème Spring.
Redis
Redis est un magasin de données en mémoire extrêmement rapide. Il peut éventuellement persister sur un disque également. Il prend en charge différentes structures de données telles que de simples paires clé-valeur, des ensembles, des files d'attente, etc.
L'utilisation de Redis en tant que file d'attente facilite grandement le partage de données entre les composants et la mise à l'échelle horizontale. Un producteur ou plusieurs producteurs peuvent pousser des données vers la file d'attente, et un consommateur ou plusieurs consommateurs peuvent extraire les données et traiter l'événement.
Plusieurs consommateurs ne peuvent pas consommer le même événement, ce qui garantit qu'un événement est traité une seule fois.
Avantages de l'utilisation de Redis comme file d'attente de messages :
- Exécution parallèle de tâches discrètes de manière non bloquante
- Belle performance
- La stabilité
- Surveillance et débogage faciles
- Mise en œuvre et utilisation faciles
Des règles:
- L'ajout d'une tâche à la file d'attente devrait être plus rapide que le traitement de la tâche elle-même.
- Consommer des tâches devrait être plus rapide que de les produire (et sinon, ajouter plus de consommateurs).
Intégration de printemps avec Redis
Les étapes suivantes décrivent la création d'un exemple d'application pour expliquer comment utiliser Spring Integration avec Redis.
Disons que vous avez une application qui permet aux utilisateurs de publier des messages. Et vous voulez créer une fonctionnalité de suivi. Une autre exigence est que chaque fois que quelqu'un publie un message, tous les abonnés doivent être informés via un canal de communication (par exemple, e-mail ou notification push).
Une façon de mettre en œuvre cela consiste à envoyer un e-mail à chaque abonné une fois que l'utilisateur publie quelque chose. Mais que se passe-t-il lorsque l'utilisateur a 1 000 abonnés ? Et quand 1 000 utilisateurs publient quelque chose en 10 secondes, chacun ayant 1 000 followers ? En outre, la publication de l'éditeur attendra-t-elle que tous les e-mails soient envoyés ?
Les systèmes distribués résolvent ce problème.
Ce problème spécifique peut être résolu en utilisant une file d'attente. Le service A (le producteur), qui est responsable de la publication des messages, se chargera de le faire. Il publiera un message et poussera un événement avec la liste des utilisateurs qui doivent recevoir un e-mail et le message lui-même. La liste des utilisateurs pourrait être récupérée dans le service B, mais pour simplifier cet exemple, nous l'enverrons à partir du service A.
Il s'agit d'une opération asynchrone. Cela signifie que le service qui publie n'aura pas à attendre pour envoyer des e-mails.
Le service B (le consommateur) extrait l'événement de la file d'attente et le traite. De cette façon, nous pourrions facilement faire évoluer nos services et nous pourrions avoir n
consommateurs envoyant des e-mails (événements de traitement).
Commençons donc par une implémentation au service du producteur. Les dépendances nécessaires sont :
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-redis</artifactId> </dependency>
Ces trois dépendances Maven sont nécessaires :
- Jedis est un client Redis.
- La dépendance Spring Data Redis facilite l'utilisation de Redis en Java. Il fournit des concepts Spring familiers tels qu'une classe de modèle pour l'utilisation de l'API de base et un accès aux données de type référentiel léger.
- Spring Integration Redis fournit une extension du modèle de programmation Spring pour prendre en charge les modèles d'intégration d'entreprise bien connus.
Ensuite, nous devons configurer le client Jedis :
@Configuration public class RedisConfig { @Value("${redis.host}") private String redisHost; @Value("${redis.port:6379}") private int redisPort; @Bean public JedisPoolConfig poolConfig() { JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(128); return poolConfig; } @Bean public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig poolConfig) { final JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); connectionFactory.setHostName(redisHost); connectionFactory.setPort(redisPort); connectionFactory.setPoolConfig(poolConfig); connectionFactory.setUsePool(true); return connectionFactory; } }
L'annotation @Value
signifie que Spring injectera la valeur définie dans les propriétés de l'application dans le champ. Cela signifie que les valeurs redis.host
et redis.port
doivent être définies dans les propriétés de l'application.
Maintenant, nous devons définir le message que nous voulons envoyer à la file d'attente. Un exemple de message simple pourrait ressembler à :
@Getter @Setter @Builder public class PostPublishedEvent { private String postUrl; private String postTitle; private List<String> emails; }
Remarque : Project Lombok (https://projectlombok.org/) fournit les @Getter
, @Setter
, @Builder
et de nombreuses autres annotations pour éviter d'encombrer le code avec des getters, des setters et d'autres éléments triviaux. Vous pouvez en savoir plus à ce sujet dans cet article de Toptal.
Le message lui-même sera enregistré au format JSON dans la file d'attente. Chaque fois qu'un événement est publié dans la file d'attente, le message sera sérialisé en JSON. Et lors de la consommation à partir de la file d'attente, le message sera désérialisé.
Avec le message défini, nous devons définir la file d'attente elle-même. Dans Spring Integration, cela peut être facilement fait via une configuration .xml
. La configuration doit être placée dans le resources/WEB-INF
.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration" xmlns:int-redis="http://www.springframework.org/schema/integration/redis" xsi:schemaLocation="http://www.springframework.org/schema/integration/redis http://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <int-redis:queue-outbound-channel-adapter channel="eventChannelJson" serializer="serializer" auto-startup="true" connection-factory="redisConnectionFactory" queue="my-event-queue" /> <int:gateway service-interface="org.toptal.queue.RedisChannelGateway" error-channel="errorChannel" default-request-channel="eventChannel"> <int:default-header name="topic" value="queue"/> </int:gateway> <int:channel/> <int:channel/> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> <int:object-to-json-transformer input-channel="eventChannel" output-channel="eventChannelJson"/> </beans>
Dans la configuration, vous pouvez voir la partie "int-redis:queue-outbound-channel-adapter". Ses propriétés sont :
- id : le nom du bean du composant.
- canal :
MessageChannel
à partir duquel ce point de terminaison reçoit des messages. - connection-factory : une référence à un bean
RedisConnectionFactory
. - file d'attente : le nom de la liste Redis sur laquelle l'opération push basée sur la file d'attente est effectuée pour envoyer des messages Redis. Cet attribut est mutuellement exclusif avec queue-expression.
- queue-expression : une expression SpEL pour déterminer le nom de la liste Redis en utilisant le message entrant au moment de l'exécution comme variable
#root
. Cet attribut est mutuellement exclusif avec la file d'attente. - sérialiseur : une référence de bean
RedisSerializer
. Par défaut, il s'agit d'unJdkSerializationRedisSerializer
. Toutefois, pour les charges utilesString
, unStringRedisSerializer
est utilisé si une référence de sérialiseur n'est pas fournie. - extract-payload : spécifiez si ce point de terminaison doit envoyer uniquement la charge utile à la file d'attente Redis ou l'intégralité du message. Sa valeur par défaut est
true
. - left-push : spécifiez si ce point de terminaison doit utiliser la poussée gauche (lorsque
true
) ou la poussée droite (lorsquefalse
) pour écrire des messages dans la liste Redis. Si la valeur est true, la liste Redis agit comme une file d'attente FIFO lorsqu'elle est utilisée avec un adaptateur de canal entrant de file d'attente Redis par défaut. Définissez surfalse
pour une utilisation avec un logiciel qui lit à partir de la liste avec pop gauche ou pour obtenir un ordre de message semblable à une pile. Sa valeur par défaut esttrue
.
L'étape suivante consiste à définir la passerelle, qui est mentionnée dans la configuration .xml
. Pour une passerelle, nous utilisons la classe RedisChannelGateway
du package org.toptal.queue
.

StringRedisSerializer
est utilisé pour sérialiser le message avant de l'enregistrer dans Redis. Toujours dans la configuration .xml
, nous avons défini la passerelle et défini RedisChannelGateway
comme service de passerelle. Cela signifie que le bean RedisChannelGateway
pourrait être injecté dans d'autres beans. Nous avons défini la propriété default-request-channel
car il est également possible de fournir des références de canal par méthode en utilisant l'annotation @Gateway
. Définition de classe :
public interface RedisChannelGateway { void enqueue(PostPublishedEvent event); }
Pour câbler cette configuration dans notre application, nous devons l'importer. Ceci est implémenté dans la classe SpringIntegrationConfig
.
@ImportResource("classpath:WEB-INF/event-queue-config.xml") @AutoConfigureAfter(RedisConfig.class) @Configuration public class SpringIntegrationConfig { }
L'annotation @ImportResource
est utilisée pour importer des fichiers de configuration Spring .xml
dans @Configuration
. Et l'annotation @AutoConfigureAfter
est utilisée pour indiquer qu'une configuration automatique doit être appliquée après d'autres classes de configuration automatique spécifiées.
Nous allons maintenant créer un service et implémenter la méthode qui enqueue
événements en file d'attente dans la file d'attente Redis.
public interface QueueService { void enqueue(PostPublishedEvent event); }
@Service public class RedisQueueService implements QueueService { private RedisChannelGateway channelGateway; @Autowired public RedisQueueService(RedisChannelGateway channelGateway) { this.channelGateway = channelGateway; } @Override public void enqueue(PostPublishedEvent event) { channelGateway.enqueue(event); } }
Et maintenant, vous pouvez facilement envoyer un message à la file d'attente en utilisant la méthode enqueue
de QueueService
.
Les files d'attente Redis sont simplement des listes avec un ou plusieurs producteurs et consommateurs. Pour publier un message dans une file d'attente, les producteurs utilisent la commande Redis LPUSH
. Et si vous surveillez Redis (indice : tapez redis-cli monitor
), vous pouvez voir que le message est ajouté à la file d'attente :
"LPUSH" "my-event-queue" "{\"postUrl\":\"test\",\"postTitle\":\"test\",\"emails\":[\"test\"]}"
Maintenant, nous devons créer une application consommateur qui extraira ces événements de la file d'attente et les traitera. Le service consommateur a besoin des mêmes dépendances que le service producteur.
Nous pouvons maintenant réutiliser la classe PostPublishedEvent
pour désérialiser les messages.
Nous devons créer la configuration de la file d'attente et, encore une fois, elle doit être placée dans le resources/WEB-INF
. Le contenu de la configuration de la file d'attente est :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration" xmlns:int-redis="http://www.springframework.org/schema/integration/redis" xsi:schemaLocation="http://www.springframework.org/schema/integration/redis http://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <int-redis:queue-inbound-channel-adapter channel="eventChannelJson" queue="my-event-queue" serializer="serializer" auto-startup="true" connection-factory="redisConnectionFactory"/> <int:channel/> <int:channel> <int:queue/> </int:channel> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> <int:json-to-object-transformer input-channel="eventChannelJson" output-channel="eventChannel" type="com.toptal.integration.spring.model.PostPublishedEvent"/> <int:service-activator input-channel="eventChannel" ref="RedisEventProcessingService" method="process"> <int:poller fixed-delay="10" time-unit="SECONDS" max-messages-per-poll="500"/> </int:service-activator> </beans>
Dans la configuration .xml
, int-redis:queue-inbound-channel-adapter
peut avoir les propriétés suivantes :
- id : le nom du bean du composant.
- channel : Le
MessageChannel
auquel nous envoyons des messages depuis ce point de terminaison. - auto-startup : un attribut
SmartLifecycle
pour spécifier si ce point de terminaison doit démarrer automatiquement après le démarrage du contexte d'application ou non. Sa valeur par défaut esttrue
. - phase : un attribut
SmartLifecycle
pour spécifier la phase dans laquelle ce point de terminaison sera démarré. Sa valeur par défaut est0
. - connection-factory : une référence à un bean
RedisConnectionFactory
. - file d'attente : le nom de la liste Redis sur laquelle l'opération pop basée sur la file d'attente est effectuée pour obtenir les messages Redis.
- error-channel : Le
MessageChannel
auquel nous enverrons lesErrorMessages
avecExceptions
à partir de la tâche d'écoute duEndpoint
. Par défaut, leMessagePublishingErrorHandler
sous-jacent utilise leerrorChannel
par défaut du contexte d'application. - serializer : la référence du bean
RedisSerializer
. Il peut s'agir d'une chaîne vide, ce qui signifie qu'il n'y a pas de sérialiseur. Dans ce cas, l'byte[]
du message Redis entrant est envoyé au canal en tant que charge utile duMessage
. Par défaut, il s'agit d'unJdkSerializationRedisSerializer
. - receive-timeout : délai d'attente en millisecondes pour que l'opération pop attende un message Redis de la file d'attente. Sa valeur par défaut est 1 seconde.
- recovery-interval : durée en millisecondes pendant laquelle la tâche d'écoute doit dormir après des exceptions sur l'opération pop avant de redémarrer la tâche d'écoute.
- expect-message : spécifiez si ce point de terminaison s'attend à ce que les données de la file d'attente Redis contiennent des messages entiers. Si cet attribut est défini sur
true
, le sérialiseur ne peut pas être une chaîne vide car les messages nécessitent une certaine forme de désérialisation (sérialisation JDK par défaut). Sa valeur par défaut estfalse
. - task-executor : une référence à un bean Spring
TaskExecutor
(ou standard JDK 1.5+ Executor). Il est utilisé pour la tâche d'écoute sous-jacente. Par défaut, unSimpleAsyncTaskExecutor
est utilisé. - right-pop : spécifiez si ce point de terminaison doit utiliser right pop (lorsque
true
) ou left pop (lorsquefalse
) pour lire les messages de la liste Redis. Sitrue
, la liste Redis agit comme une file d'attente FIFO lorsqu'elle est utilisée avec un adaptateur de canal sortant de file d'attente Redis par défaut. Définir surfalse
pour une utilisation avec un logiciel qui écrit dans la liste avec une poussée droite ou pour obtenir un ordre de message semblable à une pile. Sa valeur par défaut esttrue
.
La partie importante est "l'activateur de service", qui définit le service et la méthode à utiliser pour traiter l'événement.'
En outre, le json-to-object-transformer
a besoin d'un attribut de type afin de transformer JSON en objets, défini ci-dessus sur type="com.toptal.integration.spring.model.PostPublishedEvent"
.
Encore une fois, pour câbler cette configuration, nous aurons besoin de la classe SpringIntegrationConfig
, qui peut être la même qu'avant. Et enfin, nous avons besoin d'un service qui traitera réellement l'événement.
public interface EventProcessingService { void process(PostPublishedEvent event); } @Service("RedisEventProcessingService") public class RedisEventProcessingService implements EventProcessingService { @Override public void process(PostPublishedEvent event) { // TODO: Send emails here, retry strategy, etc :) } }
Une fois que vous avez exécuté l'application, vous pouvez voir dans Redis :
"BRPOP" "my-event-queue" "1"
Conclusion
Avec Spring Integration et Redis, créer une application de microservices Spring n'est pas aussi intimidant qu'il le serait normalement. Avec un peu de configuration et une petite quantité de code passe-partout, vous pouvez construire les fondations de votre architecture de microservice en un rien de temps.
Même si vous ne prévoyez pas de supprimer entièrement votre projet Spring actuel et de passer à une nouvelle architecture, avec l'aide de Redis, il est très simple d'obtenir d'énormes améliorations de performances avec les files d'attente.