Comunicación de microservicios: un tutorial de integración de Spring con Redis
Publicado: 2022-03-11La arquitectura de microservicios es un enfoque muy popular en el diseño e implementación de aplicaciones web altamente escalables. La comunicación dentro de una aplicación monolítica entre componentes generalmente se basa en llamadas a métodos o funciones dentro del mismo proceso. Una aplicación basada en microservicios, por otro lado, es un sistema distribuido que se ejecuta en varias máquinas.
La comunicación entre estos microservicios es importante para tener un sistema estable y escalable. Hay varias maneras de hacer esto. La comunicación basada en mensajes es una forma de hacerlo de manera confiable.
Cuando se utiliza la mensajería, los componentes interactúan entre sí intercambiando mensajes de forma asíncrona. Los mensajes se intercambian a través de canales.
Cuando el Servicio A quiere comunicarse con el Servicio B, en lugar de enviarlo directamente, A lo envía a un canal específico. Cuando el Servicio B quiere leer el mensaje, recoge el mensaje de un canal de mensajes en particular.
En este tutorial de integración de Spring, aprenderá cómo implementar la mensajería en una aplicación de Spring usando Redis. Se le guiará a través de una aplicación de ejemplo donde un servicio envía eventos en la cola y otro servicio procesa estos eventos uno por uno.
Integración de primavera
El proyecto Spring Integration amplía el marco Spring para proporcionar soporte para la mensajería entre aplicaciones basadas en Spring o dentro de ellas. Los componentes están conectados entre sí a través del paradigma de mensajería. Es posible que los componentes individuales no sean conscientes de otros componentes en la aplicación.
Spring Integration proporciona una amplia selección de mecanismos para comunicarse con sistemas externos. Los adaptadores de canal son uno de esos mecanismos utilizados para la integración unidireccional (envío o recepción). Y las puertas de enlace se utilizan para escenarios de solicitud/respuesta (entrante o saliente).
Apache Camel es una alternativa muy utilizada. La integración de Spring generalmente se prefiere en los servicios basados en Spring existentes, ya que es parte del ecosistema de Spring.
redis
Redis es un almacén de datos en memoria extremadamente rápido. Opcionalmente, también puede persistir en un disco. Admite diferentes estructuras de datos como simples pares clave-valor, conjuntos, colas, etc.
El uso de Redis como una cola facilita mucho el intercambio de datos entre componentes y la escala horizontal. Un productor o varios productores pueden enviar datos a la cola, y un consumidor o varios consumidores pueden extraer los datos y procesar el evento.
Varios consumidores no pueden consumir el mismo evento; esto garantiza que un evento se procese una vez.
Beneficios de usar Redis como una cola de mensajes:
- Ejecución paralela de tareas discretas sin bloqueo
- Gran actuación
- Estabilidad
- Fácil monitoreo y depuración
- Fácil implementación y uso
Normas:
- Agregar una tarea a la cola debería ser más rápido que procesar la tarea en sí.
- Consumir tareas debería ser más rápido que producirlas (y si no, agregar más consumidores).
Integración de Spring con Redis
A continuación, se describe la creación de una aplicación de muestra para explicar cómo usar Spring Integration con Redis.
Supongamos que tiene una aplicación que permite a los usuarios publicar publicaciones. Y desea crear una función de seguimiento. Otro requisito es que cada vez que alguien publique una publicación, todos los seguidores deben ser notificados a través de algún canal de comunicación (por ejemplo, correo electrónico o notificación automática).
Una forma de implementar esto es enviar un correo electrónico a cada seguidor una vez que el usuario publique algo. Pero, ¿qué sucede cuando el usuario tiene 1.000 seguidores? ¿Y cuando 1.000 usuarios publican algo en 10 segundos, cada uno de los cuales tiene 1.000 seguidores? Además, ¿la publicación del editor esperará hasta que se envíen todos los correos electrónicos?
Los sistemas distribuidos resuelven este problema.
Este problema específico podría resolverse utilizando una cola. El servicio A (el productor), que es responsable de publicar publicaciones, solo hará eso. Publicará una publicación y enviará un evento con la lista de usuarios que necesitan recibir un correo electrónico y la publicación en sí. La lista de usuarios podría obtenerse en el servicio B, pero para simplificar este ejemplo, la enviaremos desde el servicio A.
Esta es una operación asíncrona. Esto significa que el servicio que está publicando no tendrá que esperar para enviar correos electrónicos.
El servicio B (el consumidor) extraerá el evento de la cola y lo procesará. De esta manera, podríamos escalar fácilmente nuestros servicios y podríamos tener n
consumidores enviando correos electrónicos (procesando eventos).
Entonces, comencemos con una implementación en el servicio del productor. Las dependencias necesarias son:
<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>
Estas tres dependencias de Maven son necesarias:
- Jedis es un cliente de Redis.
- La dependencia de Spring Data Redis facilita el uso de Redis en Java. Proporciona conceptos familiares de Spring, como una clase de plantilla para el uso de API central y acceso a datos de estilo de repositorio ligero.
- Spring Integration Redis proporciona una extensión del modelo de programación Spring para admitir los conocidos patrones de integración empresarial.
A continuación, debemos configurar el cliente 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; } }
La anotación @Value
significa que Spring inyectará el valor definido en las propiedades de la aplicación en el campo. Esto significa que los valores de redis.host
y redis.port
deben definirse en las propiedades de la aplicación.
Ahora, necesitamos definir el mensaje que queremos enviar a la cola. Un mensaje de ejemplo simple podría verse así:
@Getter @Setter @Builder public class PostPublishedEvent { private String postUrl; private String postTitle; private List<String> emails; }
Nota: Project Lombok (https://projectlombok.org/) proporciona @Getter
, @Setter
, @Builder
y muchas otras anotaciones para evitar saturar el código con getters, setters y otras cosas triviales. Puede obtener más información al respecto en este artículo de Toptal.
El mensaje en sí se guardará en formato JSON en la cola. Cada vez que se publica un evento en la cola, el mensaje se serializa en JSON. Y al consumir de la cola, el mensaje se deserializará.
Con el mensaje definido, necesitamos definir la cola en sí. En Spring Integration, se puede hacer fácilmente a través de una configuración .xml
. La configuración debe colocarse dentro del directorio 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>
En la configuración, puede ver la parte "int-redis:queue-outbound-channel-adapter". Sus propiedades son:
- id: El nombre del bean del componente.
- channel:
MessageChannel
desde el cual este extremo recibe mensajes. - fábrica de conexión: una referencia a un bean
RedisConnectionFactory
. - cola: el nombre de la lista de Redis en la que se realiza la operación de inserción basada en la cola para enviar mensajes de Redis. Este atributo es mutuamente excluyente con cola-expresión.
- queue-expression: una expresión SpEL para determinar el nombre de la lista de Redis utilizando el mensaje entrante en tiempo de ejecución como la variable
#root
. Este atributo es mutuamente excluyente con la cola. - serializador: una referencia de bean
RedisSerializer
. De forma predeterminada, es unJdkSerializationRedisSerializer
. Sin embargo, para las cargas útiles deString
, se usa unStringRedisSerializer
si no se proporciona una referencia de serializador. - extract-payload: especifique si este extremo debe enviar solo la carga útil a la cola de Redis o el mensaje completo. Su valor predeterminado es
true
. - empuje izquierdo: especifique si este extremo debe usar empuje izquierdo (cuando
true
) o empuje derecho (cuando esfalse
) para escribir mensajes en la lista de Redis. Si es verdadero, la lista Redis actúa como una cola FIFO cuando se usa con un adaptador de canal de entrada de cola Redis predeterminado. Establézcalo enfalse
para usarlo con software que lee de la lista con la ventana emergente izquierda o para lograr un orden de mensajes similar a una pila. Su valor predeterminado estrue
.
El siguiente paso es definir la puerta de enlace, que se menciona en la configuración .xml
. Para una puerta de enlace, usamos la clase RedisChannelGateway
del paquete org.toptal.queue
.

StringRedisSerializer
se usa para serializar el mensaje antes de guardarlo en Redis. También en la configuración .xml
, definimos la puerta de enlace y configuramos RedisChannelGateway
como un servicio de puerta de enlace. Esto significa que el bean RedisChannelGateway
podría inyectarse en otros beans. Definimos la propiedad default-request-channel
porque también es posible proporcionar referencias de canal por método mediante la anotación @Gateway
. Definición de clase:
public interface RedisChannelGateway { void enqueue(PostPublishedEvent event); }
Para conectar esta configuración a nuestra aplicación, tenemos que importarla. Esto se implementa en la clase SpringIntegrationConfig
.
@ImportResource("classpath:WEB-INF/event-queue-config.xml") @AutoConfigureAfter(RedisConfig.class) @Configuration public class SpringIntegrationConfig { }
La anotación @ImportResource
se usa para importar archivos de configuración Spring .xml
a @Configuration
. Y la anotación @AutoConfigureAfter
se usa para indicar que se debe aplicar una configuración automática después de otras clases de configuración automática especificadas.
Ahora crearemos un servicio e implementaremos el método que pondrá en enqueue
los eventos en la cola de 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); } }
Y ahora, puede enviar fácilmente un mensaje a la cola usando el método enqueue
de QueueService
.
Las colas Redis son simplemente listas con uno o más productores y consumidores. Para publicar un mensaje en una cola, los productores usan el comando LPUSH
Redis. Y si supervisa Redis (sugerencia: escriba redis-cli monitor
), puede ver que el mensaje se agrega a la cola:
"LPUSH" "my-event-queue" "{\"postUrl\":\"test\",\"postTitle\":\"test\",\"emails\":[\"test\"]}"
Ahora, necesitamos crear una aplicación de consumo que extraiga estos eventos de la cola y los procese. El servicio del consumidor necesita las mismas dependencias que el servicio del productor.
Ahora podemos reutilizar la clase PostPublishedEvent
para deserializar mensajes.
Necesitamos crear la configuración de la cola y, nuevamente, debe colocarse dentro del directorio resources/WEB-INF
. El contenido de la configuración de la cola es:
<?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>
En la configuración .xml
, int-redis:queue-inbound-channel-adapter
puede tener las siguientes propiedades:
- id: El nombre del bean del componente.
- channel: El
MessageChannel
al que enviamos mensajes desde este punto final. - inicio automático: un atributo de
SmartLifecycle
para especificar si este punto final debe iniciarse automáticamente después del inicio del contexto de la aplicación o no. Su valor predeterminado estrue
. - fase: un atributo
SmartLifecycle
para especificar la fase en la que se iniciará este punto final. Su valor por defecto es0
. - fábrica de conexión: una referencia a un bean
RedisConnectionFactory
. - cola: el nombre de la lista de Redis en la que se realiza la operación emergente basada en la cola para obtener mensajes de Redis.
- error-channel: El
MessageChannel
al que enviaremosErrorMessages
conExceptions
desde la tarea de escucha delEndpoint
. De forma predeterminada, elMessagePublishingErrorHandler
subyacente utiliza elerrorChannel
predeterminado del contexto de la aplicación. - serializer: la referencia del bean
RedisSerializer
. Puede ser una cadena vacía, lo que significa que no hay serializador. En este caso, elbyte[]
del mensaje de Redis entrante se envía al canal como la carga delMessage
. De forma predeterminada, es unJdkSerializationRedisSerializer
. - tiempo de espera de recepción: el tiempo de espera en milisegundos para que la operación emergente espere un mensaje Redis de la cola. Su valor por defecto es 1 segundo.
- recovery-interval: el tiempo en milisegundos durante el cual la tarea de escucha debe dormir después de las excepciones en la operación emergente antes de reiniciar la tarea de escucha.
- expect-message: especifique si este extremo espera que los datos de la cola de Redis contengan mensajes completos. Si este atributo se establece en
true
, el serializador no puede ser una cadena vacía porque los mensajes requieren algún tipo de deserialización (serialización JDK de forma predeterminada). Su valor predeterminado esfalse
. - task-executor: una referencia a un bean Spring
TaskExecutor
(o estándar JDK 1.5+ Executor). Se utiliza para la tarea de escucha subyacente. De forma predeterminada, se usa unSimpleAsyncTaskExecutor
. - right-pop: especifique si este punto final debe usar la derecha (cuando
true
) o la izquierda (cuando esfalse
) para leer los mensajes de la lista de Redis. Si estrue
, la lista Redis actúa como una cola FIFO cuando se usa con un adaptador de canal de salida de cola Redis predeterminado. Establézcalo enfalse
para usar con software que escribe en la lista con el botón derecho o para lograr un orden de mensajes similar a una pila. Su valor predeterminado estrue
.
La parte importante es el "activador de servicio", que define qué servicio y método se debe utilizar para procesar el evento.'
Además, json-to-object-transformer
necesita un atributo de tipo para transformar JSON en objetos, establecido arriba en type="com.toptal.integration.spring.model.PostPublishedEvent"
.
Nuevamente, para conectar esta configuración, necesitaremos la clase SpringIntegrationConfig
, que puede ser la misma que antes. Y, por último, necesitamos un servicio que realmente procese el evento.
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 :) } }
Una vez que ejecuta la aplicación, puede ver en Redis:
"BRPOP" "my-event-queue" "1"
Conclusión
Con Spring Integration y Redis, crear una aplicación de microservicios de Spring no es tan desalentador como lo sería normalmente. Con un poco de configuración y una pequeña cantidad de código repetitivo, puede construir los cimientos de su arquitectura de microservicios en muy poco tiempo.
Incluso si no planea eliminar por completo su proyecto Spring actual y cambiar a una nueva arquitectura, con la ayuda de Redis, es muy simple obtener grandes mejoras de rendimiento con las colas.