Spring Cloud Stream con RabbitMQ: microservicios basados ​​en mensajes

En este artículo, le presentaremos Spring Cloud Stream, que es un marco para crear aplicaciones de microservicios basadas en mensajes que están conectadas por un...

Visión general

En este artículo, le presentaremos Arroyo de nubes de primavera, que es un marco para crear aplicaciones de microservicio basadas en mensajes que están conectadas por intermediarios de mensajería comunes como ConejoMQ, apache kafka, etc.

Spring Cloud Stream se basa en marcos Spring existentes como Mensajería de primavera e [Integración de primavera](https://spring. io/proyectos/spring-integration). Aunque estos marcos están probados en batalla y funcionan muy bien, la implementación está estrechamente relacionada con el intermediario de mensajes utilizado. Además, a veces es difícil escalar para ciertos casos de uso.

La idea detrás de Spring Cloud Stream es un concepto muy típico de Spring Boot: hable con él de forma abstracta y deje que Spring descubra los detalles de implementación en el tiempo de ejecución en función de la configuración y la gestión de dependencias. Lo que significa que puede cambiar el agente de mensajes subrayado simplemente cambiando las dependencias y el archivo de configuración. Los diversos corredores que actualmente son compatibles se pueden encontrar aquí.

Usaremos RabbitMQ como intermediario de mensajes para este artículo. Antes de eso, repasemos algunos conceptos básicos de un intermediario y por qué podemos necesitarlo en una arquitectura orientada a microservicios.

Mensajería en Microservicios

En una arquitectura de microservicios, tenemos muchas aplicaciones pequeñas que se comunican entre sí para completar una solicitud; una de sus principales ventajas es la escalabilidad mejorada. Es bastante común que una sola solicitud pase de más de un microservicio descendente para completarse. Por ejemplo, digamos que tenemos un Servicio-A que internamente llama al Servicio-B y al Servicio-C para completar una solicitud:

spring-cloud-stream-microservice-example

Yes, there would be other components like Nube de primavera Eureka, Nube de primavera Zuul, and many more, but we are trying to focus on a particular problem with this type of architecture.

Supongamos que, por algún motivo, Service-B tarda un poco más en responder. Tal vez esté realizando una operación de E/S o una transacción de base de datos larga o llamando a diferentes servicios que requieren que sea lento de una manera que no se puede hacer más eficiente.

Ahora podemos activar más instancias de Service-B para manejar esto y está bien, pero Service-A, que en realidad es rápido, necesita esperar una respuesta de Service-B para continuar. . Esto da como resultado que Service-A no pueda recibir más solicitudes, lo que significa que también tenemos que activar varias instancias de Service-A.

Otro enfoque para abordar una situación similar es tener una arquitectura de microservicios basada en eventos. Lo que esto significa básicamente es que, en lugar de que Service-A haga llamadas a Service-B o Service-C directamente a través de HTTP, publica la solicitud o el evento en un Message Broker. Service-B y Service-C serán suscriptores de este evento en el intermediario de mensajes.

spring-cloud-stream-microservice-event-driven-example

Esto tiene muchas ventajas sobre la arquitectura de microservicio tradicional que se basa en llamadas HTTP:

  • Mejora la escalabilidad y la confiabilidad - Ahora sabemos qué servicios son verdaderos cuellos de botella en nuestra aplicación general.
  • Fomenta el acoplamiento flojo - Servicio A no necesita saber sobre Servicio-B y Servicio-C. Todo lo que debe cuidar es conectarse al intermediario de mensajes y publicar el evento. La forma en que se orqueste el evento depende de la configuración del intermediario. De esta manera, Service-A puede evolucionar de forma independiente, que es uno de los conceptos centrales de los microservicios.
  • Interactuar con el sistema heredado - Muy a menudo no podemos mover todo a una pila de tecnología más nueva. Todavía tenemos que trabajar con el sistema heredado que, aunque lento, es confiable.

RabbitMQ

Protocolo de cola de mensajes avanzado (AMQP) es un protocolo que utiliza RabbitMQ para la mensajería. Aunque RabbitMQ admite algunos otros protocolos, AMQP es el más preferible debido a la compatibilidad y al gran conjunto de funciones que ofrece.

RabbitMQ Diseño arquitectónico

spring-cloud-stream-rabbitmq-architectural

Entonces, un editor publica un mensaje en algo llamado Exchange en RabbitMQ. Exchange toma un mensaje y lo enruta a una o más colas. Los algoritmos de enrutamiento dependen del tipo de intercambio y de una clave/encabezado de enrutamiento (que se transmite junto con el mensaje). Estas reglas que conectan un intercambio a una cola se denominan enlaces.

Los enlaces pueden ser de 4 tipos:

  • Directo: asigna directamente un tipo de intercambio a una cola específica según la clave de enrutamiento.
  • Fanout: Enruta mensajes a todas las colas del intercambio enlazado.
  • Tema: enruta los mensajes a las colas (0, 1 o más) en función de coincidencias de clave de enrutamiento completas o parciales.
  • Encabezados: es similar al tipo de intercambio de temas, pero se enruta según los valores del encabezado en lugar de las claves de enrutamiento.

spring-cloud-stream-rabbitmq-architectural
[Créditos - [https://www.cloudamqp.com/](https://www.cloudamqp.com/blog/2015-05-18-part1-rabbitmq-for-beginners-what-is-rabbitmq.html# ConejoMQ%20Tema%20Intercambio)
]{.pequeña}

Esta publicación y consumo general de mensajes a través de intercambios y colas se realiza a través de un canal.

Para obtener más detalles sobre las rutas, visite este enlace.

Configuración de RabbitMQ

Instalación

Podemos descargar y configurar los binarios en base a nuestro sistema operativo desde aquí.

Sin embargo, en este artículo utilizaremos una instalación gratuita basada en la nube proporcionada por cloudamqp.com. Simplemente regístrese en el servicio e inicie sesión.

En su tablero principal, haga clic en "Crear nueva instancia":

spring-cloud-stream-cloudamqp-setup-1

Luego asigne un nombre a su instancia y continúe con el siguiente paso:

spring-cloud-stream-cloudamqp-setup-2

Luego seleccione una Región:

spring-cloud-stream-cloudamqp-setup-3

Y por último, revise la información de su instancia y haga clic en "Crear instancia" en la esquina inferior derecha:

spring-cloud-stream-cloudamqp-setup-4

Eso es todo. Ahora tiene una instalación de RabbitMQ ejecutándose en la nube. Para obtener más detalles sobre su instancia, vaya a su tablero y haga clic en la instancia recién creada:

spring-cloud-stream-cloudamqp-setup-5

Podemos ver el host desde el que podemos acceder a nuestra instancia de RaabbitMQ, como el nombre de usuario y la contraseña necesarios para conectarnos desde nuestro proyecto:

spring-cloud-stream-cloudamqp-setup-6

Usaremos "AMQP URL" en nuestra aplicación Spring para conectarnos a esta instancia, así que anótelo en alguna parte.

También puede ver la consola del administrador haciendo clic en "RabbitMQ Manager" en la esquina superior izquierda. Esto lo llevará a la administración de su instancia de RabbitMQ, que se ve así:

spring-cloud-stream-cloudamqp-setup-7

Configuración del proyecto

Ahora que nuestra configuración está lista, creemos nuestros servicios:

  • cloud-stream-producer-rabbitmq: esto actuará como un editor que enviará mensajes a RabbitMQ
  • cloud-stream-consumer-rabbitmq: esto consumirá los mensajes

La mejor manera de comenzar con un proyecto básico es usar Spring Initializr. Este será nuestro proyecto productor y usaremos puntos finales REST para publicar mensajes.

Seleccione su versión preferida de Spring Boot y agregue las dependencias "Web" y "Cloud Stream" y genere como un proyecto Maven:

spring-cloud-stream-generate-project-1

Nota: Observe el mensaje entre paréntesis en la dependencia cloud-stream. Dice que esto también requiere una dependencia de carpeta como RabbitMQ, Kafka, etc. para funcionar.

Como usaremos RabbitMQ, agregue la siguiente dependencia de Maven:

1
2
3
4
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>

Alternativamente, también podemos combinar los dos y usar Spring Cloud Stream RabbitMQ Starter:

1
2
3
4
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

Del mismo modo, cree el proyecto del consumidor, pero solo con la dependencia spring-cloud-starter-stream-rabbit.

Construyendo el Productor

Como dijimos anteriormente, el proceso general de los mensajes que pasan de un editor a la cola se realiza a través de un canal. Así que vamos a crear una interfaz HelloBinding que contenga nuestro MessageChannel llamado "greetingChannel":

1
2
3
4
5
interface HelloBinding {

    @Output("greetingChannel")
    MessageChannel greeting();
}

Dado que esto sería publicar el mensaje, usamos la anotación @Output. El nombre del método puede ser el que queramos y, por supuesto, podemos tener más de un canal en una sola interfaz.

Ahora, vamos a crear un punto final REST que envíe mensajes a este canal:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@RestController
public class ProducerController {

    private MessageChannel greet;

    public ProducerController(HelloBinding binding) {
        greet = binding.greeting();
    }

    @GetMapping("/greet/{name}")
    public void publish(@PathVariable String name) {
        String greeting = "Hello, " + name + "!";
        Message<String> msg = MessageBuilder.withPayload(greeting)
            .build();
        this.greet.send(msg);
    }
}

Arriba, creamos una clase ProducerController que tiene un atributo saludo de tipo MessageChannel. Esto se inicializa en el constructor por el método que declaramos anteriormente.

Nota: También podríamos hacer lo mismo de forma compacta, pero estamos usando nombres diferentes para darle más claridad sobre cómo se conectan las cosas.

Luego tenemos un mapeo REST simple que toma un nombre de PathVariable y crea un Message de tipo String usando MessageBuilder. Al final, usamos el método .send() en MessageChannel para publicar el mensaje.

Ahora, tenemos que decirle a Spring sobre nuestro HelloBinding, lo cual haremos en nuestra clase principal usando la anotación @EnableBinding:

1
2
3
4
5
6
7
8
@EnableBinding(HelloBinding.class)
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

spring-cloud-stream-generate-project-1

Al final, tenemos que decirle a Spring cómo conectarse a RabbitMQ (a través de la "URL de AMQP" de antes) y crear una forma de conectar el "canal de saludo" a un posible consumidor.

Ambos están definidos dentro de application.properties:

1
2
3
4
5
spring.rabbitmq.addresses=<amqp url>

spring.cloud.stream.bindings.greetingChannel.destination = greetings

server.port=8080

Construyendo el Consumidor

Ahora necesitamos escuchar el canal que creamos anteriormente, es decir, "greetingChannel". Vamos a crear un enlace para ello:

1
2
3
4
5
6
7
public interface HelloBinding {

    String GREETING = "greetingChannel";

    @Input(GREETING)
    SubscribableChannel greeting();
}

Las dos diferencias con el enlace del productor deberían ser bastante obvias. Dado que estamos consumiendo el mensaje, estamos usando la anotación SubscribableChannel y @Input para conectarnos a "greetingChannel" donde se enviarán los datos.

Ahora, creemos el método en el que realmente procesaremos los datos:

1
2
3
4
5
6
7
8
@EnableBinding(HelloBinding.class)
public class HelloListener {

    @StreamListener(target = HelloBinding.GREETING)
    public void processHelloChannelGreeting(String msg) {
        System.out.println(msg);
    }
}

Aquí, creamos una clase HelloListener que tiene un método anotado con @StreamListener, dirigido a "greetingChannel". Este método espera un String como argumento, que acabamos de registrar en la consola. También habilitamos HelloBinding aquí usando @EnableBinding en la parte superior de la clase.

Una vez más, usamos @EnableBinding aquí y no la clase principal, para mostrarle que depende de usted cómo organizar nombres, declaraciones, etc., lo que tenga más sentido para usted o su equipo.

Veamos también nuestra clase principal, que no cambiamos:

1
2
3
4
5
6
7
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

En application.properties necesitamos definir lo mismo que hicimos para el productor, excepto que se ejecutará en un puerto diferente:

1
2
3
spring.rabbitmq.addresses=<amqp url>
spring.cloud.stream.bindings.greetingChannel.destination=greetings
server.port=9090

Probándolo todo

Comencemos tanto el productor como el servicio al consumidor. Primero, produzcamos el mensaje al llegar a nuestro punto final http://localhost:8080/saludo/john.

En los registros del consumidor puede ver el mensaje:

spring-cloud-stream-consumer-logs-1

Comencemos otra instancia del servicio del consumidor (en un puerto diferente) usando el siguiente comando:

1
$ mvn spring-boot:run -Dserver.port=9091

Ahora, cuando llegamos al punto final REST del productor para publicar, vemos que ambos consumidores recibieron el mensaje:

spring-cloud-stream-consumer-logs-2

Esto puede ser lo que queremos en algunos de nuestros casos de uso. Pero, ¿y si queremos que solo un consumidor consuma un mensaje? Para eso, necesitamos crear un grupo de consumidores en application.properties de nuestro consumidor:

1
spring.cloud.stream.bindings.greetingChannel.group = greetings-group

Ahora nuevamente, ejecute 2 instancias del consumidor en diferentes puertos y verifique nuevamente publicando a través del productor:

spring-cloud-stream-consumer-logs-3

Todo esto también se puede ver visualmente en la consola del administrador RabbitMQ:

spring-cloud-stream-rabbitmq-manager-1

spring-cloud-stream-rabbitmq-manager-2

Conclusión

En este artículo, explicamos el concepto principal de la mensajería, su papel en los microservicios y cómo implementarlo usando Spring Cloud Stream. Usamos RabbitMQ como nuestro intermediario de mensajes, pero podemos usar otros intermediarios populares, como Kafka, simplemente cambiando la configuración y las dependencias.

Como siempre, el código de los ejemplos utilizados en este artículo se puede encontrar en GitHub abbitmq)