Kafka: Request/Reply with multiple consumer instances

Ahora que las arquitecturas de microservicios orientadas a eventos están mas vigentes que nunca, acompañadas como no, de lo patrones CQRS o Event Sourcing, entre otros, a nadie le extraña el uso de sistemas de mensajería publish-subscribe como medio de comunicación asíncrono y desacoplado entre aplicaciones.

Claro que siempre pueden surgir casos de uso que requieran de una comunicación síncrona y como bien habréis deducido, este es precisamente el tema que aborda el presente artículo: Detallar como funciona el modelo comunicación request-reply a través de Kafka, especialmente cuando se disponen de múltiples instancias de los actores involucrados.

Una ultima recomendación antes de emprender esta nueva aventura: en caso de no disponer de unos conocimientos básicos acerca de Kafka, echad un ojo al post escrito al respecto en este mismo blog.

Caso de uso

Se dispone de un cluster de Kubernetes vanilla con dos instancias de dos microservicios Spring Boot que deben comunicarse de forma síncrona a través de Kafka.

Request/Reply

Lo primero de todo comentar que, en esta ocasión, se pretende dar al escrito un enfoque mas teórico que practico, ya que en internet abundan multitud de artículos y repositorios de Github que muestran a las mil maravillas los pasos a realizar para llevar a cabo en esta empresa, de una forma casi transparente para el programador. Dicho esto, la pregunta obligada es, ¿cómo funciona entonces internamente este mecanismo?

Básicamente, cuando se realiza una comunicación request-reply a través de Spring Kafka, se incorporan las siguientes dos cabeceras en los mensajes:

  • REPLY_TOPIC: Tópico al que el microservicio llamado debe enviar el mensaje de respuesta.
  • CORRELATION_ID:  Identificador del consumidor del microservicio llamante, para que en caso de que múltiples consumidores escuchen del mismo tópico, puedan discriminar si son o no los destinatarios del mensaje.

Sencillo, ¿no?

Fuente Original

El verdadero reto surge cuando el microservicio llamante dispone varias instancias, ya que tan solo una de ellas debe procesar la respuesta. Pero… ¿no se encargaba de ello el CORRELATION_ID? No, esta cabecera permite discriminar entre distintos grupos de consumidores de un mismo tópico, pero no entre los distintos consumidores de un mismo grupo.

Aclaro esto, es hora de analizar las tres posibles soluciones:

A unique group id per instance

Cada instancia del microservicio llamante dispone un group-id distinto, de tal forma que todos los mensajes enviados a dicho tópico son recibidos por los todos subscriptores y posteriormente filtrados por él CORRELATION_ID.

Evidentemente esta solución no es nada optima a nivel de rendimiento, ya que todos los microservicios reciben todos los mensajes enviados al tópico, para posteriormente descartar la mayoría de ellos. Además, obliga a Kafka a mantener actualizada la información de todos estos grupos de consumidores, con el consecuente impacto en el rendimiento.

Por supuesto esta solución es inviable si el microservicio hace uso de otros patrones de comunicación a través de Kafka, como puede ser la coreografía, ya que no dispondrá de los medios necesarios para poder determinar si debe o no procesar el mensaje.

a unique reply topic per instance

Cada instancia del microservicio llamante dispone de un tópico propio en el que recibir la respuesta, de tal forma que no es necesario ningún tipo filtrado, ya que únicamente recibe los mensajes que le correspondan.

Ahora bien, a nivel de gobierno esto puede convertirse en un autentico dolor de cabeza, unido a que la auto creación de los tópicos no es precisamente una buena practica en entornos productivos. Por no hablar de que cada vez que se quiera aumentar o reducir el número de instancias del microservicio, primera deberán tomarse las acciones oportunas en Kafka, reduciendo de forma significa la agilidad de este proceso.

Evidentemente, el autoescalado de Kubernetes queda automáticamente deshabilitado, a menos que se construya una suerte de operador personalizado que se encargue de todo.

reply_partition header

Se añade en los mensajes una cabecera adicional denominada REPLY_PARTITION, en la que se define la partición a la que se debe enviar el mensaje de respuesta, de tal forma que únicamente la instancia del microservicio llamante recibe el mensaje de respuesta.

Sin lugar a dudas, esta es la opción recomendaba, ya que no añade sobrecarga ni a los microservicios ni al cluster de Kafka, al mismo tiempo que no complica en el gobierno de los tópicos.

Eso si, Spring Kafka no envía de forma automática esta cabecera, por lo que será necesario realizar una sencilla configuración adicional para ello, cómo se puede observar en el siguiente artículo.

CONCLUSIONES

En conclusión, la opción más acertada pasa claramente por incluir la cabecera REPLY_PARTITION en los mensajes, si bien desde Spring tampoco desaconsejan el uso de tópicos de respuesta propios para cada instancia.

Como siempre, antes de tomar un decision, se recomienda encarecidamente analizar en detalle el caso de uso así como los planes de crecimiento.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s