Введение в Apache Kafka с весны

Введение в Apache Kafka с весны

1. обзор

Apache Kafka - это распределенная и отказоустойчивая система потоковой обработки.

В этой статье мы рассмотрим поддержку Spring для Kafka и уровень абстракций, которые он предоставляет по сравнению с собственными клиентскими API Kafka Java.

Spring Kafka предлагает простую и типичную модель программирования шаблонов Spring сKafkaTemplate и объектами POJO, управляемыми сообщениями, через аннотацию@KafkaListener.

Дальнейшее чтение:

Узнайте, как обрабатывать потоковые данные с помощью Flink и Kafka

Read more

Пример подключения Kafka с MQTT и MongoDB

Взгляните на практический пример использования разъемов Kafka.

Read more

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 запущен и темы созданы вручную.