如何使用Apache Kafka的Spring Cloud Streams

490 阅读7分钟

TL;DR你有没有想过,像谷歌地图的实时交通功能是如何工作的?这些系统必须实时收集和处理数据。这些系统的架构通常涉及到一个数据管道,处理和传输数据以进一步处理,直到到达客户端。在这篇文章中,我们将通过一个使用Kafka流的简单例子看到类似的东西。示例应用程序可以在这里找到。

Spring Cloud Stream简介

Spring Cloud Stream是一个框架,旨在支持由Apache Kafka、RabbitMQ等各种消息传递系统提供的流处理。该框架允许你创建处理逻辑,而不需要处理任何特定的平台。它可以帮助你建立高度可扩展的事件驱动的微服务,并使用这些消息传递系统连接。

该框架提供了一个灵活的编程模型,该模型建立在已经建立和熟悉的Spring习性和最佳实践上。它的工作方式很简单,你必须为你所使用的消息系统提供实现(称为Binder实现)。Spring云流支持。

以及其他一些。上面的链接会带你到粘合剂的实现。在这篇文章中,我们将研究一个简单的应用,它使用Kafka Streams作为流处理器监听主题上的事件,处理数据,并将其发布到外发主题上。

Apache Kafka简介

Apache Kafka是一个分布式发布-订阅消息系统。它是一个发布和订阅记录流的系统,类似于一个消息队列。Kafka适用于离线和在线消息消费。它是容错的,稳健的,并且有很高的吞吐量。Kafka以集群的形式运行在一个或多个服务器上,可以跨越多个数据中心。Kafka集群在称为主题的类别中存储记录流。每个记录由一个键、一个值和一个时间戳组成。关于主题、生产者API、消费者API和事件流的更多信息,请访问此链接

Kafka流简介

Kafka流是一个库,可以用来消费数据、处理数据和产生新的数据,所有这些都是实时的。它在一个连续的、永不停息的数据流上工作。考虑一下股票市场的例子。股票价格每秒都在波动,为了能够向客户提供实时价值,你会使用类似Kafka流的东西。

先决条件。

  1. Java 11的基本知识。
  2. Spring Boot的基本了解。
  3. Apache Kafka有基本的了解。
  4. DockerDocker Compose用于本地运行Kafka。

设置Spring Boot应用程序

让我们首先在Spring boot Initializr的帮助下创建一个Spring Boot项目,然后在我们喜欢的IDE中打开该项目。选择Gradle项目和Java语言。最后但并非最不重要的是,选择Spring boot版本 2.5.4.填入项目元数据,然后点击生成。

对于Spring Cloud,我们需要在我们的项目中配置Spring Kafka和Kafka Streams。 gradle.build:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.apache.kafka:kafka-streams'
    implementation 'org.springframework.kafka:spring-kafka'
}

让我们来设置Kafka的配置。我们需要定义一些参数,说明我们要如何序列化和反序列化数据。这个配置很容易设置和理解。由于我们的应用程序将监听一个主题,并将输出到一个不同的主题,我们的应用程序既是生产者也是消费者。所以,我们需要为生产者和消费者都定义配置。

在application.yml文件中,我们需要添加这些条目。

kafka:
  bootstrap-servers: localhost:9092
  properties:
    schema.registry.url: http://localhost:8081

  producer:
    client-id: ${spring.application.name}
    key-serializer: org.apache.kafka.common.serialization.StringSerializer
    value-serializer: org.apache.kafka.common.serialization.StringSerializer

  consumer:
    client-id: ${spring.application.name}
    group-id: ${spring.application.name}-group
    key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    auto-offset-reset: earliest

  streams:
    client-id: ${spring.application.name}-stream
    application-id: ${spring.application.name}
    properties:
      default.key.serde: org.apache.kafka.common.serialization.Serdes$StringSerde
      default.value.serde: org.apache.kafka.common.serialization.Serdes$StringSerde

如果你仔细看一下这个配置,我们正在为生产者、消费者和流设置序列化器和反序列化器(serde只是序列化器-反序列化器的简称)。这是我们在Spring boot项目中唯一需要的设置。

让我们开始创建生产者、消费者和流处理器。我在这里举了一个简单的例子。我们使用一个调度器,每2秒产生一个随机数。

@Component
@AllArgsConstructor
public class NumberProducer {

    NumberPublisher numberPublisher;

    @Scheduled(fixedRate = 2000)
    public void produceIntStream() {
        Random random = new Random();
        numberPublisher.produce(random.nextInt(1000));
    }
}

数字发布者是实际的发布者,将数据放在一个主题上。我们为消息和数据(在我们的例子中是一个随机数)设置一个密钥。

@Slf4j
@Component
@AllArgsConstructor
public class NumberPublisher {

    private final KafkaTemplate<String, String> kafkaTemplate;

    public void produce(Integer randomNumber) {
        String s = "Odd";
        if (randomNumber % 2 == 0) s = "Even";
        System.out.println("Produced number: " + randomNumber);
        kafkaTemplate.send(INPUT_TOPIC_NAME, s, String.valueOf(randomNumber));
    }
}

密钥被定义为一个字符串,根据数字,它是偶数或奇数。我们使用Kafka模板来发送消息;这来自spring-kafka库。它抽象出了发布和消费消息的逻辑。

接下来,我们设置我们的流处理器,监听发布者投放消息的主题。这是它变得有趣的地方。我们监听 INPUT_TOPIC然后处理数据。在这种情况下,流处理器的工作是过滤掉奇数,只发送偶数到 OUTPUT_TOPIC.

@Configuration
@EnableKafkaStreams
public class KafkaStream {

    public static final String OUTPUT_TOPIC_NAME = "even-number-topic";
    public static final String INPUT_TOPIC_NAME = "number-topic";

    @Bean
    public KStream<String, String> evenNumbersStream(StreamsBuilder kStreamBuilder) {
        KStream<String, String> input = kStreamBuilder.stream(INPUT_TOPIC_NAME);

        KStream<String, String> output = input.filter((key, value) -> key.equals("Even"));

        output.to(OUTPUT_TOPIC_NAME);
        return output;
    }
}

你可能想知道我们方法的返回类型中的KStream是什么。我将在这里做一个简单的概述,因为这不在本文的范围之内。

KStream -> 一个只需追加的Kafka流。当你提供具有相同键的数据时,它不会更新之前的记录。它提供了几种对数据处理非常有用的操作,如过滤器、地图、分区、flatMap等。你可以在这里阅读更多关于KStreams的信息。

最后,当我们处理完数据后,我们把它放在一个 OUTGOING_TOPIC.为了简单和完整起见,我在我们的应用程序中听取该主题。一般来说,情况不会是这样的,因为会有另一个应用程序从该主题中进行消费,因此被称为 OUTGOING_TOPIC.

@Component
@EnableKafka
public class EvenNumberConsumer {

    @KafkaListener(topics = OUTPUT_TOPIC_NAME)
    public void receive(String value) {
        System.out.println("Received number: " + value);
    }
}

应用程序的代码已经完成。让我们在本地设置Kafka。

在本地设置Kafka

设置Kafka很简单,但它需要一些依赖来运行,你只需要使用下面的docker-compose文件,它将在本地启动Kafka服务器。将 docker compose.yml到版本库的根目录。使用启动所需的依赖性。 docker-compose up.

version: '3.7'

services:
  kafka:
    image: confluentinc/cp-kafka:5.5.0
    container_name: kafka
    hostname: kafka
    restart: always
    environment:
      KAFKA_LISTENERS: LISTENER_DOCKER_INTERNAL://:29092,LISTENER_DOCKER_EXTERNAL://:9092
      KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka:29092,LISTENER_DOCKER_EXTERNAL://localhost:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    ports:
      - 9092:9092
      - 9999:9999
    depends_on:
      - zookeeper

  zookeeper:
    container_name: zookeeper
    hostname: zookeeper
    image: confluentinc/cp-zookeeper:5.5.0
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
    ports:
      - 2181:2181

  schema-registry:
    image: confluentinc/cp-schema-registry:5.5.0
    hostname: schema-registry
    container_name: schema-registry
    restart: always
    environment:
      SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: "kafka:29092"
      SCHEMA_REGISTRY_HOST_NAME: schema-registry
      SCHEMA_REGISTRY_LISTENERS: "http://0.0.0.0:8081"
    ports:
      - 8081:8081
    depends_on:
      - zookeeper

就这样吧!

验证事件的发送、处理和接收

运行Spring Boot应用程序。

  1. 从Repo中克隆示例代码。转到根目录。
  2. 使用Gradle插件,使用项目目录中的命令运行你的Spring Boot应用程序。
./gradlew bootRun

只要运行该应用程序。你应该看到像这样的日志。

Received number: 910
Received number: 320
Received number: 16
Received number: 526
Received number: 76
Received number: 936
Received number: 642
Produced number: 510
Received number: 510
Produced number: 996
Received number: 996
Produced number: 897

总结

Spring Cloud Stream为创建可以处理流并向不同主题发布数据的应用提供了一种简单而方便的方法。你可以建立微服务,使用Kafka消息相互交谈,并像在单个应用程序中处理数据一样处理数据。

在这篇文章中,我们已经了解了如何构建一个使用Kafka流的Spring Cloud Stream应用。我们看到了Spring Cloud Stream是如何提供一种简单的方式来设置和运行一个可以消费、处理和发布消息到Kafka主题的应用,而不用为配置每个主题而烦恼。用这么少的代码,我们可以做很多事情。

你可以在Github上参考文章中使用的资源库。