Múltiples consumidores se suscriben a un tema de Kafka (usando @KafkaListener y KafkaTemplate)

Registros : 465

Escenario : un productor publica un mensaje sobre un tema y varios consumidores se suscriben al tema de Kafka. Cada Consumidor especifica un Grupo de Consumidores específico, de modo que un mensaje puede ser consumido por múltiples Grupos de Consumidores diferentes.

Versión : JDK 1.8, Spring Boot 2.6.3, kafka_2.12-2.8.0, spring-kafka-2.8.2。

Instalación del clúster de Kafka : https://blog.csdn.net/zhangbeizhen18/article/details/131156084

1. Conceptos básicos

Tema : Kafka clasifica los mensajes según el tema, y ​​cada mensaje publicado en Kafka debe especificar un tema.

Productor : El productor de mensajes, el cliente que envía mensajes al Broker.

Consumidor : Consumidor de mensajes, el cliente que lee los mensajes del Broker.

Grupo de consumidores : cada consumidor pertenece a un grupo de consumidores específico y un mensaje puede ser consumido por varios grupos de consumidores diferentes, pero solo un consumidor en un grupo de consumidores puede consumir el mensaje.

publicar : publicar, usar Producer para escribir datos en Kafka.

subscribe : Suscríbete, usa Consumer para leer datos de Kafka.

2. Configurar la información de Kafka en microservicios

(1) Agregar dependencias en pom.xml

<dependency>
  <groupId>org.springframework.kafka</groupId>
  <artifactId>spring-kafka</artifactId>
  <version>2.8.2</version>
</dependency>

Tenga en cuenta : la capa inferior del marco spring-kafka utiliza clientes kafka nativos. Este ejemplo corresponde a la versión: 3.0.0.

(2) Configurar la información de Kafka en application.yml

Para la configuración, consulte la configuración en el sitio web oficial: https://kafka.apache.org/documentation/

(1) contenido de configuración application.yml

spring:
  kafka:
    #kafka集群的IP和端口,格式:(ip:port)
    bootstrap-servers:
      - 192.168.19.161:29092
      - 192.168.19.162:29092
      - 192.168.19.163:29092
    #生产者
    producer:
      #客户端发送服务端失败的重试次数
      retries: 2
      #多个记录被发送到同一个分区时,生产者将尝试将记录一起批处理成更少的请求.
      #此设置有助于提高客户端和服务器的性能,配置控制默认批量大小(以字节为单位)
      batch-size: 16384
      #生产者可用于缓冲等待发送到服务器的记录的总内存字节数(以字节为单位)
      buffer-memory: 33554432
      #指定key使用的序列化类
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      #指定value使用的序列化类
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      #生产者producer要求leader节点在考虑完成请求之前收到的确认数,用于控制发送记录在服务端的持久化
      #acks=0,设置为0,则生产者producer将不会等待来自服务器的任何确认.该记录将立即添加到套接字(socket)缓冲区并视为已发送.在这种情况下,无法保证服务器已收到记录,并且重试配置(retries)将不会生效(因为客户端通常不会知道任何故障),每条记录返回的偏移量始终设置为-1.
      #acks=1,设置为1,leader节点会把记录写入本地日志,不需要等待所有follower节点完全确认就会立即应答producer.在这种情况下,在follower节点复制前,leader节点确认记录后立即失败的话,记录将会丢失.
      #acks=all,acks=-1,leader节点将等待所有同步复制副本完成再确认记录,这保证了只要至少有一个同步复制副本存活,记录就不会丢失.
      acks: -1
    consumer:
      #开启consumer的偏移量(offset)自动提交到Kafka
      enable-auto-commit: true
      #consumer的偏移量(offset)自动提交的时间间隔,单位毫秒
      auto-commit-interval: 1000
      #在Kafka中没有初始化偏移量或者当前偏移量不存在情况
      #earliest,在偏移量无效的情况下,自动重置为最早的偏移量
      #latest,在偏移量无效的情况下,自动重置为最新的偏移量
      #none,在偏移量无效的情况下,抛出异常.
      auto-offset-reset: latest
      #一次调用poll返回的最大记录条数
      max-poll-records: 500
      #请求阻塞的最大时间(毫秒)
      fetch-max-wait: 500
      #请求应答的最小字节数
      fetch-min-size: 1
      #心跳间隔时间(毫秒)
      heartbeat-interval: 3000
      #指定key使用的反序列化类
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      #指定value使用的反序列化类
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer

(2) Análisis

La clase de configuración se anota automáticamente en el paquete Spring Boot: spring-boot-autoconfigure-2.6.3.jar.

类:org.springframework.boot.autoconfigure.kafka.KafkaProperties。

Use la anotación @ConfigurationProperties para que sea efectiva, el prefijo es: spring.kafka.

El marco spring-kafka tiene diferentes configuraciones para operar la versión independiente de Kafka y la versión de clúster de Kafka:

En el atributo bootstrap-servers, la versión autónoma se configura con un par IP:puerto. La versión de clúster se puede configurar con varias direcciones IP: pares de puertos.

(3) Lógica de carga

Cuando se inicia el microservicio Spring Boot, Spring Boot leerá la información de configuración de application.yml, encontrará KafkaProperties en spring-boot-autoconfigure-2.6.3.jar de acuerdo con el contenido de configuración y las inyectará en las propiedades correspondientes. Una vez que se inicia el microservicio Spring Boot, la información de configuración de KafkaProperties se puede usar sin problemas en el entorno Spring.

El marco spring-kafka de Spring inyecta información de configuración de KafkaProperties en el productor de operaciones de KafkaTemplate, Producer.

El marco spring-kafka de Spring usa KafkaProperties y @KafkaListener para operar el consumidor de Kafka.

3. Productor (ChangjiangDeltaCityProducerController)

(1) código de muestra

@RestController
@RequestMapping("/hub/example/delta/producer")
@Slf4j
public class ChangjiangDeltaCityProducerController {
  //1.注入KafkaTemplate
  @Autowired
  private KafkaTemplate<String, String> kafkaTemplate;
  //2.定义Kafka的Topic
  private final String topicName = "hub-topic-city-delta";
  @GetMapping("/f01_1")
  public Object f01_1(String msgContent) {
    try {
      //3.获取业务数据对象
      String uuid=UUID.randomUUID().toString().replace("-","");
      long now=System.currentTimeMillis();
      String msgKey = "delta" + ":" + uuid + ":" + now;
      MsgDto msgDto = MsgDto.buildDto(uuid,now,msgContent);
      String msgData = JSONObject.toJSONString(msgDto);
      log.info("KafkaProducer向Kafka集群的Topic: {},写入Key:", topicName);
      log.info(msgKey);
      log.info("KafkaProducer向Kafka集群的Topic: {},写入Data:", topicName);
      log.info(msgData);
      //4.使用KafkaTemplate向Kafka集群写入数据(topic,key,data)
      kafkaTemplate.send(topicName, msgKey, msgData);
    } catch (Exception e) {
      log.info("Producer写入Topic异常.");
      e.printStackTrace();
    }
    return "写入成功";
  }
}

(2) Código de análisis

Use KafkaTemplate para escribir datos de cadena JSON en el tema del clúster de Kafka: hub-topic-city-delta, publique un mensaje y consúmalo para los consumidores suscritos.

4. Consumidor 1 (Consumidor de la ciudad de Hangzhou)

(1) código de muestra

@Component
@Slf4j
public class HangzhouCityConsumer {
  // 1.定义Kafka的Topic
  private final String topicName = "hub-topic-city-delta";
  // 2.使用@KafkaListener监听Kafka集群的Topic
  @KafkaListener(
      topics = {topicName},
      groupId = "hub-topic-city-delta-group-hangzhou")
  public void consumeMsg(ConsumerRecord<?, ?> record) {
    try {
        //3.KafkaConsumer从集群中监听的消息存储在ConsumerRecord
        String msgKey= (String) record.key();
        String msgData = (String) record.value();
        log.info("HangzhouCityConsumer从Kafka集群中的Topic:{},消费的原始数据的Key:",topicName);
        log.info(msgKey);
        log.info("HangzhouCityConsumer从Kafka集群中的Topic:{},消费的原始数据的Data:",topicName);
        log.info(msgData);
    } catch (Exception e) {
        log.info("HangzhouCityConsumer消费Topic异常.");
        e.printStackTrace();
    }
  }
}

(2) Código de análisis

Utilice los temas de atributos de @KafkaListener para especificar el tema que escuchar: hub-topic-city-delta.

Utilice el atributo groupId de @KafkaListener para especificar el grupo de consumidores: hub-topic-city-delta-group-hangzhou.

5. Consumidor 2 (ShanghaiCityConsumer)

(1) código de muestra

@Component
@Slf4j
public class ShanghaiCityConsumer {
  // 1.定义Kafka的Topic
  private final String topicName = "hub-topic-city-delta";
  // 2.使用@KafkaListener监听Kafka集群的Topic
  @KafkaListener(
          topics = {topicName},
          groupId = "hub-topic-city-delta-group-shanghai")
  public void consumeMsg(ConsumerRecord<?, ?> record) {
    try {
        //3.KafkaConsumer从集群中监听的消息存储在ConsumerRecord
        String msgKey = (String) record.key();
        String msgData = (String) record.value();
        log.info("ShanghaiCityConsumer从Kafka集群中的Topic:{},消费的原始数据的Key:", topicName);
        log.info(msgKey);
        log.info("ShanghaiCityConsumer从Kafka集群中的Topic:{},消费的原始数据的Data:", topicName);
        log.info(msgData);
    } catch (Exception e) {
        log.info("ShanghaiCityConsumer消费Topic异常.");
        e.printStackTrace();
    }
  }
}

(2) Código de análisis

Utilice los temas de atributos de @KafkaListener para especificar el tema que escuchar: hub-topic-city-delta.

Utilice el atributo groupId de @KafkaListener para especificar el grupo de consumidores: hub-topic-city-delta-group-shanghai.

6. Prueba

(1) Use la prueba Postman para llamar al productor para escribir datos

请求RUL:http://127.0.0.1:18208/hub-208-kafka/hub/example/delta/producer/f01_1

Parámetros: msgContent="El cinturón económico del delta del río Yangtze es poderoso"

(2) Registro del productor

KafkaProducer向Kafka集群的Topic: hub-topic-city-delta,写入Key:
delta:b5a669933f4041588d53d53c22888943:1687789723647
KafkaProducer向Kafka集群的Topic: hub-topic-city-delta,写入Data:
{"msgContent":"长三角经济带实力强大","publicTime":"2023-06-26 22:28:43","uuid":"b5a669933f4041588d53d53c22888943"}

(3) Registro de consumidores

HangzhouCityConsumer从Kafka集群中的Topic:hub-topic-city-delta,消费的原始数据的Key:
delta:b5a669933f4041588d53d53c22888943:1687789723647
HangzhouCityConsumer从Kafka集群中的Topic:hub-topic-city-delta,消费的原始数据的Data:
{"msgContent":"长三角经济带实力强大","publicTime":"2023-06-26 22:28:43","uuid":"b5a669933f4041588d53d53c22888943"}

(4) Registro del consumidor 2

ShanghaiCityConsumer从Kafka集群中的Topic:hub-topic-city-delta,消费的原始数据的Key:
delta:b5a669933f4041588d53d53c22888943:1687789723647
ShanghaiCityConsumer从Kafka集群中的Topic:hub-topic-city-delta,消费的原始数据的Data:
{"msgContent":"长三角经济带实力强大","publicTime":"2023-06-26 22:28:43","uuid":"b5a669933f4041588d53d53c22888943"}

(5. Conclusión

Cada Consumidor especifica un Grupo de Consumidores específico, y un mensaje puede ser consumido por múltiples Grupos de Consumidores diferentes.

7. Clase auxiliar

@Data
@Builder
public class MsgDto implements Serializable {
  private String uuid;
  private String publicTime;
  private String msgContent;
  public static MsgDto buildDto(String uuid,
                      long publicTime,
                      String msgContent) {
      return builder().uuid(uuid)
          .publicTime(DateUtil.formatDateTime(new Date(publicTime)))
          .msgContent(msgContent).build();
  }
}

Arriba, gracias.

26 de junio de 2023

Supongo que te gusta

Origin blog.csdn.net/zhangbeizhen18/article/details/131407224
Recomendado
Clasificación