sábado, 20 de abril de 2019

Kafka con Spring Boot. Topicos y Colas.

Kafka es un gran framework que se usa para comunicación entre aplicaciones. El objetivo de este artículo no es estudiar las características de Kafka ni de Spring boot, para eso hay mucha documentación en las paginas web de cada framework. El objetivo de este articulo es explicar una forma sencilla de montar Kafka, de como usarlo para comunicar aplicaciones Spring boot, como realizar una comunicación por tópicos (es decir todos los consumidores escuchan el mensaje) y realizar comunicación por colas (es decir solo un consumidor escucha el mensaje) para los programadores que están acostumbrados a otros frameworks como ActiveMQ o RabbitMQ.

Kafka no funciona de la misma manera que los framework mas comunes(ActiveMQ o RabbitMQ) por lo que no hay una forma directa de hacer lo mismo pero en este artículo voy a describir como aplicar comportamientos equivalentes.


Para comprender correctamente este articulo necesitas tener conocimientos básicos de Docker y Spring boot.

La forma mas sencilla de montar un "servidor" de Kafka es usar Docker, se puede usar el siguiente fichero de docker-compose:


docker-compose.yml 

version: '3'
services:
  zookeeper:
    image: wurstmeister/zookeeper
    ports:
      - "2181:2181"
    hostname: zookeeper
  kafka:
    image: wurstmeister/kafka
    command: [start-kafka.sh]
    ports:
      - "9092:9092"
    hostname: kafka
    environment:
      KAFKA_ADVERTISED_HOST_NAME: localhost
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_PORT: 9092
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    depends_on:
      - "zookeeper"



Una vez ejecutado este fichero, ya esta disponible Kafka en el puerto 9092.

El código del productor de mensajes y de consumidor de mensajes esta en https://github.com/rumberomelo/productor-consumidor-basico

Si descargas el código y lo ejecutas (y has arrancado Kafka como indico al principio) puedes comprobar el funcionamiento mas sencillo, un productor que escribe un mensaje de texto que lee un unico consumidor. Para hacer funcionar el ejemplo solo tienes que hacer la siguiente petición:

http://localhost:9000/envia/mensaje?texto=hola

Esta petición hará que el productor escriba en el tópico de Kafka "TOPIC_EJEMPLO" el mensaje "hola". Puedes comprobar en el log del consumidor que lo ha leido ya que aparecera un mensaje como INFO 47114 --- [ntainer#0-0-C-1] com.rumberomelo.kafka.consumer.Consumer  : Se ha consumido el mensaje hola.

Este ejemplo muestra como un consumidor lee un mensaje que produce un productor. Si lanzaramos un consumidor exactamente igual pueden pasar dos cosas (depende de como se configure Kafka):

  • El nuevo consumidor es el unico que lee todos los mensajes de productor.
  • El nuevo consumidor siempre queda a la espera (nunca lee ningun mensaje) y el viejo consumidor lee todos los mensajes.
Esto se puede comprobar en el ejemplo https://github.com/rumberomelo/productor-dos-consumidores y ver que solo un consumidor lee el mensaje. Si aplicamos un pequeño cambio y cambiamos el nombre de los grupos de cada uno de los consumidores como indican estos dibujos:





Al arrancar de nuevo los ejemplos se puede comprobar como los dos consumidores reciben en mensaje mirando el log de los consumidores. Este comportamiento es igual al tópico de frameworks como RabbitMQ o ActiveMQ.

Volvemos a dejar el nombre de los grupos como estaba. ¿Como se hace para que un mensaje lo lea uno de los consumidores (no siempre el mismo)? Se hace indicando las particiones en las que se quiere dividir el tópico. Cuando un productor escribe en un tópico que no existe (que es lo que se ha hecho al principio de este articulo) se crea automáticamente sin particiones, si tienes varias particiones entonces los consumidores se reparten dichas particiones de tal manera que cada consumidor solo lee de una de las particiones asignadas y como un mensaje se puede colocar aleatoriamente en cualquier particiones ya se ha logrado que sea un consumidor cualquiera el que lea el mensaje.

Para probarlo, se debe entrar en el contenedor de Kafka:


$ docker exec -it articulokafta_kafka_1 bash

Se puede comprobar que no hay particiones:


$ kafka-topics.sh --describe --zookeeper zookeeper:2181 --topic TOPIC_EJEMPLO

Topic:TOPIC_EJEMPLO PartitionCount:1 ReplicationFactor:1 Configs:
Topic: TOPIC_EJEMPLO Partition: 0 Leader: 1001 Replicas: 1001 Isr: 1001

Se añaden dos particiones:


$ kafka-topics.sh --alter --zookeeper zookeeper:2181 --topic TOPIC_EJEMPLO --partitions 2


Al arrancar de nuevo el ejemplo https://github.com/rumberomelo/productor-dos-consumidores y dejar el nombre de los grupos como estaban originalmente, si se hace la prueba de lanzar varios mensajes entonces se puede comprobar como a veces los lee un consumidor y a veces otro.