API Gateway (REST) ​​+ Microservicios controlados por eventos

16

Tengo un montón de microservicios cuya funcionalidad expongo a través de una API REST de acuerdo con el patrón API Gateway. Como estos microservicios son aplicaciones Spring Boot, estoy usando Spring AMQP para lograr una comunicación síncrona de estilo RPC entre estos microservicios. Las cosas han ido bien hasta ahora. Sin embargo, cuanto más leo sobre arquitecturas de microservicios controladas por eventos y veo proyectos como Spring Cloud Stream, más convencido estoy de que puedo estar haciendo las cosas de manera incorrecta con el enfoque síncrono RPC (particularmente porque necesitaré esto para escalar para responder a cientos o miles de solicitudes por segundo de aplicaciones de clientes).

Entiendo el punto detrás de una arquitectura basada en eventos. Lo que no entiendo es cómo usar un patrón de este tipo cuando estoy sentado detrás de un modelo (REST) ​​que espera una respuesta a cada solicitud. Por ejemplo, si tengo mi puerta de enlace API como un microservicio y otro microservicio que almacena y administra a los usuarios, ¿cómo podría modelar una cosa como una GET /users/1de manera puramente basada en eventos?

Tony E. Stark
fuente

Respuestas:

9

Repite después de mi:

Los eventos REST y asíncronos no son alternativas. Son completamente ortogonales.

Puede tener uno, o el otro, o ambos, o ninguno. Son herramientas completamente diferentes para dominios de problemas completamente diferentes. De hecho, la comunicación de solicitud-respuesta de propósito general es absolutamente capaz de ser asíncrona, controlada por eventos y tolerante a fallas .


Como ejemplo trivial, el protocolo AMQP envía mensajes a través de una conexión TCP. En TCP, cada paquete debe ser reconocido por el receptor . Si un remitente de un paquete no recibe un ACK para ese paquete, sigue reenviando ese paquete hasta que sea ACK o hasta que la capa de aplicación "se rinda" y abandone la conexión. Este es claramente un modelo de solicitud-respuesta no tolerante a fallas porque cada "solicitud de envío de paquetes" debe tener una "respuesta de reconocimiento de paquetes" que lo acompaña, y la falta de respuesta da como resultado que falle la conexión completa. ¡Sin embargo, AMQP, un protocolo estandarizado y ampliamente adoptado para la mensajería asincrónica tolerante a fallas, se comunica a través de TCP! ¿Lo que da?

El concepto central en juego aquí es que la mensajería escalable y poco tolerante a fallas se define por los mensajes que envía , no por cómo los envía . En otras palabras, el acoplamiento suelto se define en la capa de aplicación .

Veamos a dos partes que se comunican directamente con HTTP RESTful o indirectamente con un agente de mensajes AMQP. Supongamos que la Parte A desea cargar una imagen JPEG a la Parte B, que afinará, comprimirá o mejorará la imagen. La Parte A no necesita la imagen procesada de inmediato, pero requiere una referencia para su uso y recuperación en el futuro. Aquí hay una manera que podría ir en REST:

  • La Parte A envía un POSTmensaje de solicitud HTTP a la Parte B conContent-Type: image/jpeg
  • La Parte B procesa la imagen (durante mucho tiempo si es grande) mientras la Parte A espera, posiblemente haciendo otras cosas
  • La Parte B envía un 201 Createdmensaje de respuesta HTTP a la Parte A con un Content-Location: <url>encabezado que enlaza con la imagen procesada
  • La Parte A considera su trabajo realizado ya que ahora tiene una referencia a la imagen procesada
  • En algún momento en el futuro, cuando la Parte A necesite la imagen procesada, la OBTENDRÁ usando el enlace del Content-Locationencabezado anterior

El 201 Createdcódigo de respuesta le dice a un cliente que no solo su solicitud fue exitosa, sino que también creó un nuevo recurso. En una respuesta 201, el Content-Locationencabezado es un enlace al recurso creado. Esto se especifica en RFC 7231 Secciones 6.3.2 y 3.1.4.2.

Ahora, veamos cómo funciona esta interacción sobre un hipotético protocolo RPC además de AMQP:

  • La Parte A envía a un agente de mensajes AMQP (llámelo Messenger) un mensaje que contiene la imagen e instrucciones para enrutarla a la Parte B para su procesamiento, luego responde a la Parte A con una dirección de algún tipo para la imagen
  • La fiesta A espera, posiblemente haciendo otras cosas
  • Messenger envía el mensaje original de la Parte A a la Parte B
  • La parte B procesa el mensaje
  • La Parte B envía a Messenger un mensaje que contiene una dirección para la imagen procesada e instrucciones para enrutar ese mensaje a la Parte A
  • Messenger envía a la Parte A el mensaje de la Parte B que contiene la dirección de la imagen procesada
  • La Parte A considera su trabajo realizado ya que ahora tiene una referencia a la imagen procesada
  • En algún momento en el futuro, cuando la Parte A necesite la imagen, recupera la imagen utilizando la dirección (posiblemente enviando mensajes a otra parte)

¿Ves el problema aquí? En ambos casos, la Parte A no puede obtener una dirección imagen hasta después de la Parte B procesa la imagen . Sin embargo, la Parte A no necesita la imagen de inmediato y, por todos los derechos, ¡no podría importarle menos si el procesamiento aún está terminado!

Podemos arreglar esto con bastante facilidad en el caso de AMQP haciendo que la Parte B le diga a A que B aceptó la imagen para procesarla, dándole a A una dirección de dónde estará la imagen después de que se complete el procesamiento. Luego, la Parte B puede enviar un mensaje a A en algún momento en el futuro indicando que el procesamiento de la imagen ha finalizado. Mensajería AMQP al rescate!

Excepto adivina qué: puedes lograr lo mismo con REST . En el ejemplo de AMQP, cambiamos el mensaje "aquí está la imagen procesada" a un mensaje "la imagen se está procesando, puede obtenerla más tarde". Para hacerlo en RESTful HTTP, usaremos el 202 Acceptedcódigo y Content-Locationnuevamente:

  • La Parte A envía un POSTmensaje HTTP a la Parte B conContent-Type: image/jpeg
  • La Parte B inmediatamente envía una 202 Acceptedrespuesta que contiene algún tipo de contenido de "operación asincrónica" que describe si el procesamiento ha finalizado y dónde estará disponible la imagen cuando termine el procesamiento. También se incluye un Content-Location: <link>encabezado que, en una 202 Acceptedrespuesta, es un enlace al recurso representado por el cuerpo de la respuesta. ¡En este caso, eso significa que es un enlace a nuestra operación asincrónica!
  • La Parte A considera su trabajo realizado ya que ahora tiene una referencia a la imagen procesada
  • En algún momento en el futuro, cuando la Parte A necesite la imagen procesada, primero OBTENDRÁ el recurso de operación asíncrona vinculado al Content-Locationencabezado para determinar si el procesamiento ha finalizado. Si es así, la Parte A luego usa el enlace en la operación asíncrona para OBTENER la imagen procesada.

La única diferencia aquí es que en el modelo AMQP, la Parte B le dice a la Parte A cuándo se realiza el procesamiento de la imagen. Pero en el modelo REST, la Parte A verifica si el procesamiento se realiza justo antes de que realmente necesite la imagen. Estos enfoques son igualmente escalables . A medida que el sistema se hace más grande, el número de mensajes enviados tanto en el AMQP asíncrono como en las estrategias REST asíncronas aumenta con una complejidad asintótica equivalente. La única diferencia es que el cliente está enviando un mensaje adicional en lugar del servidor.

Pero el enfoque REST tiene algunos trucos más bajo la manga: descubrimiento dinámico y negociación de protocolo . Considere cómo comenzaron las interacciones REST de sincronización y asíncrona. La Parte A envió exactamente la misma solicitud a la Parte B, y la única diferencia fue el tipo particular de mensaje de éxito con el que respondió la Parte B. ¿Qué pasaría si la Parte A quisiera elegir si el procesamiento de imágenes es sincrónico o asincrónico? ¿Qué sucede si la Parte A no sabe si la Parte B es incluso capaz de procesar de forma asíncrona?

Bueno, ¡HTTP ya tiene un protocolo estandarizado para esto! Se llama Preferencias HTTP, específicamente la respond-asyncpreferencia de RFC 7240 Sección 4.1. Si la Parte A desea una respuesta asincrónica, incluye un Prefer: respond-asyncencabezado con su solicitud POST inicial. Si la Parte B decide honrar esta solicitud, envía una 202 Acceptedrespuesta que incluye a Preference-Applied: respond-async. De lo contrario, la Parte B simplemente ignora el Preferencabezado y lo devuelve 201 Createdcomo lo haría normalmente.

Esto permite a la Parte A negociar con el servidor, adaptándose dinámicamente a cualquier implementación de procesamiento de imagen con la que esté hablando. Además, el uso de enlaces explícitos significa que la Parte A no tiene que conocer a ninguna otra parte que no sea B: ningún agente de mensajes AMQP, ningún Parte M misterioso que sepa cómo convertir la dirección de la imagen en datos de imagen, ni un segundo B-Async parte si se deben realizar solicitudes síncronas y asíncronas, etc. Simplemente describe lo que necesita, lo que opcionalmente le gustaría, y luego reacciona a los códigos de estado, contenido de respuesta y enlaces. AñadirCache-Controlencabezados para obtener instrucciones explícitas sobre cuándo conservar copias locales de datos, y ahora los servidores pueden negociar con los clientes qué recursos pueden mantener las copias locales (¡o incluso fuera de línea!). Así es como se construyen microservicios tolerantes a fallas acoplados libremente en REST.

Jack
fuente
1

El hecho de que necesite o no estar basado exclusivamente en eventos depende, por supuesto, de su escenario específico. Suponiendo que realmente es necesario, puede resolver el problema de la siguiente manera:

Almacenar una copia local de solo lectura de los datos escuchando los diferentes eventos y capturando la información en sus cargas. Si bien esto le brinda lecturas rápidas (er) para esos datos, almacenados en un formulario adecuado para esa aplicación exacta, también significa que sus datos eventualmente serán consistentes en todos los servicios.

Para modelar GET /users/1con este enfoque, se podría escuchar el UserCreatedy UserUpdatedeventos, y almacenar el subconjunto útil de los datos de los usuarios en el servicio. Cuando necesite obtener esa información de los usuarios, simplemente puede consultar su almacén de datos local.

Por un minuto, supongamos que el servicio que expone el /users/punto final no publica ningún tipo de evento. En este caso, podría lograr algo similar simplemente almacenando en caché las respuestas a las solicitudes HTTP que realiza, negando así la necesidad de realizar más de 1 solicitud HTTP por usuario dentro de un período de tiempo.

Andy Hunt
fuente
Entiendo. Pero, ¿qué pasa con el manejo de errores (e informes) a los clientes en este escenario?
Tony E. Stark
Quiero decir, ¿cómo informo a los clientes de REST los errores que se producen al manejar el UserCreatedevento (por ejemplo, nombre de usuario duplicado o correo electrónico o interrupción de la base de datos).
Tony E. Stark
Depende de dónde estés realizando la acción. Si está dentro del sistema de usuario, puede hacer toda su validación, escribir en el almacén de datos allí y luego publicar el evento. De lo contrario, considero que es perfectamente aceptable realizar una solicitud HTTP estándar al /users/punto final, y permitir que el sistema publique su evento si tuvo éxito, y responder a la solicitud con la nueva entidad
Andy Hunt,
0

Con un sistema de origen de eventos, los aspectos asincrónicos normalmente entran en juego cuando se cambia algo que representa el estado, tal vez una base de datos o una vista agregada de algunos datos. Usando su ejemplo, una llamada a GET / api / users podría simplemente devolver la respuesta de un servicio que tiene una representación actualizada de una lista de usuarios en el sistema. En otro escenario, la solicitud a GET / api / users podría hacer que un servicio use la secuencia de eventos desde la última instantánea de los usuarios para crear otra instantánea y simplemente devolver los resultados. Un sistema controlado por eventos no es necesariamente puramente asíncrono desde la solicitud hasta la respuesta, sino que tiende a estar en el nivel en que los servicios deben interactuar con otros servicios. A menudo no tiene sentido devolver asincrónicamente una solicitud GET y, por lo tanto, simplemente puede devolver la respuesta de un servicio,

Lloyd Moore
fuente