Введение в Apache Kafka с весны
1. обзор
Apache Kafka - это распределенная и отказоустойчивая система потоковой обработки.
В этой статье мы рассмотрим поддержку Spring для Kafka и уровень абстракций, которые он предоставляет по сравнению с собственными клиентскими API Kafka Java.
Spring Kafka предлагает простую и типичную модель программирования шаблонов Spring сKafkaTemplate и объектами POJO, управляемыми сообщениями, через аннотацию@KafkaListener.
Дальнейшее чтение:
Построение конвейера данных с помощью Flink и Kafka
Узнайте, как обрабатывать потоковые данные с помощью Flink и Kafka
Пример подключения Kafka с MQTT и MongoDB
Взгляните на практический пример использования разъемов Kafka.
2. Установка и настройка
Чтобы загрузить и установить Kafka, обратитесь к официальному руководствуhere.
Нам также нужно добавить зависимостьspring-kafka к нашемуpom.xml:
org.springframework.kafka
spring-kafka
2.2.7.RELEASE
Последнюю версию этого артефакта можно найтиhere.
Нашим примером приложения будет приложение Spring Boot.
В этой статье предполагается, что сервер запущен с использованием конфигурации по умолчанию, и порты сервера не изменены.
3. Настройка тем
Ранее мы использовали инструменты командной строки для создания тем в Kafka, таких как:
$ bin/kafka-topics.sh --create \
--zookeeper localhost:2181 \
--replication-factor 1 --partitions 1 \
--topic mytopic
Но с введениемAdminClient в Kafka теперь мы можем создавать темы программно.
Нам нужно добавить bean-компонент SpringKafkaAdmin, который автоматически добавит темы для всех bean-компонентов типаNewTopic:.
@Configuration
public class KafkaTopicConfig {
@Value(value = "${kafka.bootstrapAddress}")
private String bootstrapAddress;
@Bean
public KafkaAdmin kafkaAdmin() {
Map configs = new HashMap<>();
configs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
return new KafkaAdmin(configs);
}
@Bean
public NewTopic topic1() {
return new NewTopic("example", 1, (short) 1);
}
}
4. Создание сообщений
Чтобы создавать сообщения, сначала нам нужно настроитьProducerFactory, который устанавливает стратегию создания экземпляров KafkaProducer.
Затем нам понадобитсяKafkaTemplate, который обертывает экземплярProducer и предоставляет удобные методы для отправки сообщений в темы Kafka.
ЭкземплярыProducer являются потокобезопасными, и поэтому использование одного экземпляра во всем контексте приложения даст более высокую производительность. Следовательно, экземплярыKakfaTemplate также являются потокобезопасными, и рекомендуется использовать один экземпляр.
4.1. Конфигурация производителя
@Configuration
public class KafkaProducerConfig {
@Bean
public ProducerFactory producerFactory() {
Map configProps = new HashMap<>();
configProps.put(
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
bootstrapAddress);
configProps.put(
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
StringSerializer.class);
configProps.put(
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
StringSerializer.class);
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean
public KafkaTemplate kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}
4.2. Публикация сообщений
Мы можем отправлять сообщения с помощью классаKafkaTemplate:
@Autowired
private KafkaTemplate kafkaTemplate;
public void sendMessage(String msg) {
kafkaTemplate.send(topicName, msg);
}
The send API returns a ListenableFuture object. Если мы хотим заблокировать поток отправки и получить результат об отправленном сообщении, мы можем вызватьget API объектаListenableFuture. Поток будет ждать результата, но замедлит продюсера.
Kafka - это платформа быстрой обработки потоков. Поэтому лучше обрабатывать результаты асинхронно, чтобы последующие сообщения не дожидались результата предыдущего сообщения. Мы можем сделать это с помощью обратного вызова:
public void sendMessage(String message) {
ListenableFuture> future =
kafkaTemplate.send(topicName, message);
future.addCallback(new ListenableFutureCallback>() {
@Override
public void onSuccess(SendResult result) {
System.out.println("Sent message=[" + message +
"] with offset=[" + result.getRecordMetadata().offset() + "]");
}
@Override
public void onFailure(Throwable ex) {
System.out.println("Unable to send message=["
+ message + "] due to : " + ex.getMessage());
}
});
}
5. Потребление сообщений
5.1. Конфигурация потребителя
Для получения сообщений нам нужно настроитьConsumerFactory иKafkaListenerContainerFactory. Как только эти bean-компоненты будут доступны в фабрике bean-компонентов Spring, потребители на основе POJO могут быть настроены с помощью аннотации@KafkaListener.
Аннотация@EnableKafka требуется в классе конфигурации, чтобы включить обнаружение аннотации@KafkaListener в управляемых компонентах Spring:
@EnableKafka
@Configuration
public class KafkaConsumerConfig {
@Bean
public ConsumerFactory consumerFactory() {
Map props = new HashMap<>();
props.put(
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
bootstrapAddress);
props.put(
ConsumerConfig.GROUP_ID_CONFIG,
groupId);
props.put(
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class);
props.put(
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class);
return new DefaultKafkaConsumerFactory<>(props);
}
@Bean
public ConcurrentKafkaListenerContainerFactory
kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
}
5.2. Потребление сообщений
@KafkaListener(topics = "topicName", groupId = "foo")
public void listen(String message) {
System.out.println("Received Messasge in group foo: " + message);
}
Multiple listeners can be implemented for a topic, каждый с другим идентификатором группы. Кроме того, один потребитель может прослушивать сообщения на разные темы:
@KafkaListener(topics = "topic1, topic2", groupId = "foo")
Spring также поддерживает получение одного или нескольких заголовков сообщений с помощью аннотации@Header в слушателе:
@KafkaListener(topics = "topicName")
public void listenWithHeaders(
@Payload String message,
@Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition) {
System.out.println(
"Received Message: " + message"
+ "from partition: " + partition);
}
5.3. Получение сообщений из определенного раздела
Как вы могли заметить, мы создали темуexample только с одним разделом. Однако для темы с несколькими разделами@KafkaListener может явно подписаться на конкретный раздел темы с начальным смещением:
@KafkaListener(
topicPartitions = @TopicPartition(topic = "topicName",
partitionOffsets = {
@PartitionOffset(partition = "0", initialOffset = "0"),
@PartitionOffset(partition = "3", initialOffset = "0")
}))
public void listenToParition(
@Payload String message,
@Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition) {
System.out.println(
"Received Messasge: " + message"
+ "from partition: " + partition);
}
ПосколькуinitialOffset был отправлен в 0 в этом прослушивателе, все ранее использованные сообщения из разделов 0 и 3 будут повторно использоваться каждый раз при инициализации этого прослушивателя. Если установка смещения не требуется, мы можем использовать свойствоpartitions аннотации@TopicPartition, чтобы установить только разделы без смещения:
@KafkaListener(topicPartitions
= @TopicPartition(topic = "topicName", partitions = { "0", "1" }))
5.4. Добавление фильтра сообщений для слушателей
Слушатели могут быть настроены на использование определенных типов сообщений, добавив пользовательский фильтр. Это можно сделать, установивRecordFilterStrategy наKafkaListenerContainerFactory:
@Bean
public ConcurrentKafkaListenerContainerFactory
filterKafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
factory.setRecordFilterStrategy(
record -> record.value().contains("World"));
return factory;
}
Затем можно настроить прослушиватель на использование этой фабрики контейнеров:
@KafkaListener(
topics = "topicName",
containerFactory = "filterKafkaListenerContainerFactory")
public void listen(String message) {
// handle message
}
В этом слушателе всеmessages matching the filter will be discarded.
6. Пользовательские конвертеры сообщений
До сих пор мы рассматривали только отправку и получение строк в качестве сообщений. Однако мы также можем отправлять и получать пользовательские объекты Java. Для этого необходимо настроить соответствующий сериализатор вProducerFactory и десериализатор вConsumerFactory.
Давайте посмотрим на простой класс bean-компонентов,, который мы будем отправлять в виде сообщений:
public class Greeting {
private String msg;
private String name;
// standard getters, setters and constructor
}
6.1. Создание собственных сообщений
В этом примере мы будем использоватьJsonSerializer. Давайте посмотрим на код дляProducerFactory иKafkaTemplate:
@Bean
public ProducerFactory greetingProducerFactory() {
// ...
configProps.put(
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
JsonSerializer.class);
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean
public KafkaTemplate greetingKafkaTemplate() {
return new KafkaTemplate<>(greetingProducerFactory());
}
Этот новыйKafkaTemplate можно использовать для отправки сообщенияGreeting:
kafkaTemplate.send(topicName, new Greeting("Hello", "World"));
6.2. Использование пользовательских сообщений
Точно так же давайте изменимConsumerFactory иKafkaListenerContainerFactory, чтобы правильно десериализовать приветственное сообщение:
@Bean
public ConsumerFactory greetingConsumerFactory() {
// ...
return new DefaultKafkaConsumerFactory<>(
props,
new StringDeserializer(),
new JsonDeserializer<>(Greeting.class));
}
@Bean
public ConcurrentKafkaListenerContainerFactory
greetingKafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(greetingConsumerFactory());
return factory;
}
Сериализатор и десериализатор JSON spring-kafka использует библиотекуJackson, которая также является дополнительной зависимостью maven для проекта spring-kafka. Итак, добавим это к нашемуpom.xml:
com.fasterxml.jackson.core
jackson-databind
2.9.7
Вместо использования последней версии Jackson рекомендуется использовать версию, которая добавляется кpom.xml spring-kafka.
Наконец, нам нужно написать слушателя для приема сообщенийGreeting:
@KafkaListener(
topics = "topicName",
containerFactory = "greetingKafkaListenerContainerFactory")
public void greetingListener(Greeting greeting) {
// process greeting message
}
7. Заключение
В этой статье мы рассмотрели основы поддержки Spring для Apache Kafka. Мы кратко рассмотрели классы, которые используются для отправки и получения сообщений.
Полный исходный код этой статьи можно найти вover on GitHub. Перед выполнением кода убедитесь, что сервер Kafka запущен и темы созданы вручную.