Lectura relacionada:
-
¡Empezando! ¡La lengua vernácula te lleva a conocer Kafka!
-
Te lleva a experimentar un Kafka en 5 minutos
Paso 1: crear proyecto
Puede ser creado directamente por Spring Initializr proporcionado por Spring o directamente creado por IDEA.
Paso 2: configurar Kafka
Configure la información básica de Kafka a través del archivo de configuración application.yml
server:
port: 9090
spring:
kafka:
consumer:
bootstrap-servers: localhost:9092
# 配置消费者消息offset是否自动重置(消费者重连会能够接收最开始的消息)
auto-offset-reset: earliest
producer:
bootstrap-servers: localhost:9092
# 发送的对象信息变为json格式
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
kafka:
topic:
my-topic: my-topic
my-topic2: my-topic2
Clases de configuración adicionales de Kafka:
package cn.javaguide.springbootkafka01sendobjects.config;
import org.apache.kafka.clients.admin.NewTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.support.converter.RecordMessageConverter;
import org.springframework.kafka.support.converter.StringJsonMessageConverter;
@Configuration
public class KafkaConfig {
@Value("${kafka.topic.my-topic}")
String myTopic;
@Value("${kafka.topic.my-topic2}")
String myTopic2;
/**
* JSON消息转换器
*/
@Bean
public RecordMessageConverter jsonConverter() {
return new StringJsonMessageConverter();
}
/**
* 通过注入一个 NewTopic 类型的 Bean 来创建 topic,如果 topic 已存在,则会忽略。
*/
@Bean
public NewTopic myTopic() {
return new NewTopic(myTopic, 2, (short) 1);
}
@Bean
public NewTopic myTopic2() {
return new NewTopic(myTopic2, 1, (short) 1);
}
}
Cuando lleguemos a este paso, puede intentar ejecutar el proyecto. Después de ejecutarlo correctamente, encontrará que Spring Boot creará dos temas para usted:
- mi-tema: el número de particiones es 2, el número de réplicas es 1
- my-topic2: el número de particiones es 1, el número de réplicas es 1
"Como se mencionó en la sección anterior:
"kafka-topics --describe --zookeeper zoo1:2181
Vista de comandos o directamente a través del complemento de administración visual Kafka-Kafkalytic proporcionado por IDEA
Paso 3: crea la clase de entidad de mensaje que se enviará
package cn.javaguide.springbootkafka01sendobjects.entity;
public class Book {
private Long id;
private String name;
public Book() {
}
public Book(Long id, String name) {
this.id = id;
this.name = name;
}
省略 getter/setter以及 toString方法
}
Paso 4: crea un productor para enviar mensajes
"Este paso es relativamente largo y optimizará el código del productor paso a paso.
"
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
@Service
public class BookProducerService {
private static final Logger logger = LoggerFactory.getLogger(BookProducerService.class);
private final KafkaTemplate<String, Object> kafkaTemplate;
public BookProducerService(KafkaTemplate<String, Object> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public void sendMessage(String topic, Object o) {
kafkaTemplate.send(topic, o);
}
}
Podemos completar fácilmente el envío del mensaje utilizando el método de KafkaTemplate
llamada proporcionado por Kafka para send()
acceder al tema y al contenido del mensaje a enviar:
kafkaTemplate.send(topic, o);
Si queremos saber el resultado del mensaje enviado, el sendMessage
método se escribe así:
public void sendMessage(String topic, Object o) {
try {
SendResult<String, Object> sendResult = kafkaTemplate.send(topic, o).get();
if (sendResult.getRecordMetadata() != null) {
logger.info("生产者成功发送消息到" + sendResult.getProducerRecord().topic() + "-> " + sendResult.getProducerRecord().value().toString());
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
Sin embargo, este tipo de método de envío síncrono no se recomienda y no aprovecha Future
las características del objeto.
KafkaTemplate
El send()
método de llamada en realidad devuelve el ListenableFuture
objeto.
send()
El código fuente del método es el siguiente:
@Override
public ListenableFuture<SendResult<K, V>> send(String topic, @Nullable V data) {
ProducerRecord<K, V> producerRecord = new ProducerRecord<>(topic, data);
return doSend(producerRecord);
}
ListenableFuture
Spring es la que proporciona la Future
interfaz heredada .
ListenableFuture
El código fuente del método es el siguiente:
public interface ListenableFuture<T> extends Future<T> {
void addCallback(ListenableFutureCallback<? super T> var1);
void addCallback(SuccessCallback<? super T> var1, FailureCallback var2);
default CompletableFuture<T> completable() {
CompletableFuture<T> completable = new DelegatingCompletableFuture(this);
this.addCallback(completable::complete, completable::completeExceptionally);
return completable;
}
}
Continuar optimizando el sendMessage
método
public void sendMessage(String topic, Object o) {
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, o);
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
@Override
public void onSuccess(SendResult<String, Object> sendResult) {
logger.info("生产者成功发送消息到" + topic + "-> " + sendResult.getProducerRecord().value().toString());
}
@Override
public void onFailure(Throwable throwable) {
logger.error("生产者发送消息:{} 失败,原因:{}", o.toString(), throwable.getMessage());
}
});
}
Utilice expresiones lambda para continuar con la optimización:
public void sendMessage(String topic, Object o) {
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, o);
future.addCallback(result -> logger.info("生产者成功发送消息到topic:{} partition:{}的消息", result.getRecordMetadata().topic(), result.getRecordMetadata().partition()),
ex -> logger.error("生产者发送消失败,原因:{}", ex.getMessage()));
}
Estudiemos brevemente el send(String topic, @Nullable V data)
método nuevamente .
Cuando usamos el send(String topic, @Nullable V data)
método, en realidad ProducerRecord
enviaremos un nuevo objeto,
@Override
public ListenableFuture<SendResult<K, V>> send(String topic, @Nullable V data) {
ProducerRecord<K, V> producerRecord = new ProducerRecord<>(topic, data);
return doSend(producerRecord);
}
ProducerRecord
Hay varios métodos de construcción en la clase:
public ProducerRecord(String topic, V value) {
this(topic, null, null, null, value, null);
}
public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V
......
}
Si queremos traer la marca de tiempo (timestamp), clave y otra información al enviar, el sendMessage()
método se puede escribir así:
public void sendMessage(String topic, Object o) {
// 分区编号最好为 null,交给 kafka 自己去分配
ProducerRecord<String, Object> producerRecord = new ProducerRecord<>(topic, null, System.currentTimeMillis(), String.valueOf(o.hashCode()), o);
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(producerRecord);
future.addCallback(result -> logger.info("生产者成功发送消息到topic:{} partition:{}的消息", result.getRecordMetadata().topic(), result.getRecordMetadata().partition()),
ex -> logger.error("生产者发送消失败,原因:{}", ex.getMessage()));
}
Paso 5: crear consumidores que consuman mensajes
Al usar @KafkaListener
anotaciones en los métodos para monitorear mensajes, cuando hay mensajes, se consumirán a través de la encuesta.
import cn.javaguide.springbootkafka01sendobjects.entity.Book;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;
@Service
public class BookConsumerService {
@Value("${kafka.topic.my-topic}")
private String myTopic;
@Value("${kafka.topic.my-topic2}")
private String myTopic2;
private final Logger logger = LoggerFactory.getLogger(BookProducerService.class);
private final ObjectMapper objectMapper = new ObjectMapper();
@KafkaListener(topics = {"${kafka.topic.my-topic}"}, groupId = "group1")
public void consumeMessage(ConsumerRecord<String, String> bookConsumerRecord) {
try {
Book book = objectMapper.readValue(bookConsumerRecord.value(), Book.class);
logger.info("消费者消费topic:{} partition:{}的消息 -> {}", bookConsumerRecord.topic(), bookConsumerRecord.partition(), book.toString());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
@KafkaListener(topics = {"${kafka.topic.my-topic2}"}, groupId = "group2")
public void consumeMessage2(Book book) {
logger.info("消费者消费{}的消息 -> {}", myTopic2, book.toString());
}
}
Paso 6: crea un controlador de descanso
import cn.javaguide.springbootkafka01sendobjects.entity.Book;
import cn.javaguide.springbootkafka01sendobjects.service.BookProducerService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.atomic.AtomicLong;
@RestController
@RequestMapping(value = "/book")
public class BookController {
@Value("${kafka.topic.my-topic}")
String myTopic;
@Value("${kafka.topic.my-topic2}")
String myTopic2;
private final BookProducerService producer;
private AtomicLong atomicLong = new AtomicLong();
BookController(BookProducerService producer) {
this.producer = producer;
}
@PostMapping
public void sendMessageToKafkaTopic(@RequestParam("name") String name) {
this.producer.sendMessage(myTopic, new Book(atomicLong.addAndGet(1), name));
this.producer.sendMessage(myTopic2, new Book(atomicLong.addAndGet(1), name));
}
}
Step7: prueba
ingrese el comando:
curl -X POST -F 'name=Java' http://localhost:9090/book
El efecto impreso por la consola es el siguiente:
my-topic tiene 2 particiones. Cuando intente enviar varios mensajes, encontrará que los mensajes se enviarán a cada partición de manera más uniforme.