使用Apache Kafka创建事件驱动的Spring Boot微服务

2024年 2月 23日 50.4k 0

当今技术潮流中,事件驱动的微服务成为了一种转型的力量,其中的微服务模块通过事件实现无缝通信,提高系统的可扩展性、可适应性和敏捷性。消息队列在事件驱动架构中起着重要作用,Apache Kafka 作为高性能、可扩展和可靠的消息队列系统,被广泛应用于实时数据流处理和事件驱动架构中,因此成为了事件驱动架构中的核心技术之一。

本文介绍如何使用 Apache Kafka 构建事件驱动的微服务架构。

1 事件驱动架构简介

事件驱动架构(EDA)是一种软件设计模式,它使系统内部的组件通过生成和消费事件来相互通信。在这种架构中,事件表示系统内发生的重要事件,并可以在其他组件中触发相应的操作。这种方法可以实现松散耦合的系统,提高系统的可扩展性,并能够快速响应实时变化。

2 Apache Kafka 简介

Apache Kafka 是一个分布式、容错的消息系统,可以处理大量数据和实时流。它采用发布-订阅模型,生产者将消息发布到主题中,消费者通过订阅这些主题来接收消息。Kafka 的持久存储和副本机制确保了数据的可靠性和容错能力,是构建实时数据流处理和事件驱动架构的理想选择。

3 设置环境

在深入研究构建微服务之前,先确保已经设置了有效的工作环境。需要:

  • Java 开发工具包(JDK)
  • Gradle(如果您使用 gradle-wrapper,则无需安装系统级 gradle)
  • Docker
  • 运行实例的 Apache Kafka
  • 您喜欢的代码编辑器(例如 IntelliJ IDEA、Eclipse、VSCode)

4 具体步骤

4.1 步骤 1:设置 Spring Boot 项目

使用 Spring Initializer (https://start.spring.io/)创建一个新的 Spring Boot 项目,确保包含必要的依赖项。在项目中集成 Spring Web 以管理 Web 功能,并集成 Spring Boot DevTools 以提高开发效率。选择 Java 21 作为开发环境,以兼容新的字符串模板和 Java 虚拟线程。

创建和保存项目文件,然后使用集成开发环境(IDE)打开它们。

在开始项目实现之前,为了保证最佳性能,可以在 application.properties 文件中启用 Spring Boot 虚拟线程。通过这个配置,在处理 HTTP 连接时,可以在默认线程执行器中使用虚拟线程。这样可以提高应用程序的响应速度和处理能力,特别是在高并发场景下。

spring.threads.virtual.enabled=true

4.2 步骤 2:实现文本生产者微服务

在此微服务中,将处理文本数据的上传,并将其发布到名为 TEXT_DATA 的 Apache Kafka 主题中。

在 application.properties 中添加所需的 Apache Kafka 属性

# src/main/resources/application.properties
spring.kafka.bootstrap-servers=localhost:9092

创建所需的组件:

TextDataProducer 类:

package com.example.kafkaapp;

import org.apache.kafka.clients.admin.NewTopic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaAdmin;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import java.util.stream.Stream;

import static java.lang.StringTemplate.STR;

@Component
public class TextDataProducer {
    Logger logger = Logger.getLogger(getClass().getName());

    // 主题配置常量
    private final static int PARTITION_COUNT = 8;
    private final static String TOPIC = "TEXT-DATA";
    private final static short REPLICATION_FACTOR = 1;
    private final KafkaTemplate kafkaTemplate;

    public TextDataProducer(KafkaTemplate kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    @Autowired
    public void configureTopic(KafkaAdmin kafkaAdmin) {
        kafkaAdmin.createOrModifyTopics(new NewTopic(TOPIC, PARTITION_COUNT, REPLICATION_FACTOR));
    }

    private void sendTextMessage(String text, int lineIndex) {
        if (text == null || text.isEmpty()) {
            return;
        }
        // 将 Link 消息发送到主题,根据行索引在分区上分发
        kafkaTemplate.send(TOPIC, "KEY-" + (lineIndex % PARTITION_COUNT), text);
    }

    public void sendContentOf(File file) {
        Instant before = Instant.now();
        try (Stream lines = Files.lines(file.toPath())) {
            AtomicInteger counter = new AtomicInteger(0);
            lines.forEach(line -> sendTextMessage(line, counter.getAndIncrement()));
            Instant after = Instant.now();
            Duration duration = Duration.between(before, after);
            logger.info(STR."Streamed \{counter.get()} lines in \{duration.toMillis()} millisecond");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 该类负责生产文本数据并将其发送到名为 TEXT-DATA 的 Kafka 主题。
  • 它使用 @Component 进行注解,使其成为 Spring 管理的组件。
  • 它具有主题配置的常量,例如 PARTITION_COUNT、TOPIC 和 REPLICATION_FACTOR。
  • configureTopic 方法使用 KafkaAdmin 配置 Kafka 主题。它使用指定的设置创建或修改主题。
  • sendTextMessage 方法将文本消息发送到 Kafka 主题,并根据行索引在分区上分布消息。

TextDataProducer 类的最重要部分是 sendContentOf(File file)方法。此方法负责逐行读取给定文件的内容,并将每行发送到名为 TEXT-DATA 的 Kafka 主题。

TextDataController 类

package com.example.kafkaapp;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;

@RestController
public class TextDataController {

    private final TextDataProducer producer;

    public TextDataController(TextDataProducer producer) {
        this.producer = producer;
    }

    @PostMapping("/upload")
    public Optional uploadTextFile(@RequestParam("file") MultipartFile file) throws IOException {
        Path tempFile = Files.createTempFile(file.getOriginalFilename(), null);
        file.transferTo(tempFile);
        Thread.ofVirtual().start(() -> producer.sendContentOf(tempFile.toFile()));
        return Optional.of(tempFile.toString());
    }
}
  • 该类是 Spring REST 控制器,用于处理 HTTP 请求。
  • 它通过构造函数将 TextDataProducer bean 注入进来。
  • uploadTextFile 方法被映射用于处理 /upload 的 POST 请求。它接受一个 multipart 文件,并上传该文件。
  • 它创建一个临时文件,将上传的文件内容转移到该文件中,使用 TextDataProducer 将临时文件的内容发送到 Kafka,并返回临时文件的路径。

TextProducerApplication 类

package com.example.kafkaapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TextProducerApplication {

    public static void main(String[] args) {
        SpringApplication.run(TextProducerApplication.class, args);
    }
}
  • 是 Spring Boot 应用程序的入口点。
  • 它使用 @SpringBootApplication 注解,该注解组合了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan。
  • main 方法启动 Spring Boot 应用程序。

实现了文本生产者后,可以使用以下命令运行项目:

./gradlew bootRun

使用 Rest 客户端(如 Postman 或 SwaggerUI)将示例文本文件发布到 http://localhost:8080/upload 端点。此外,也可以在 IntelliJ IDEA 中使用以下原始 HTTP 命令发布文件:

### 发送包含文本和文件字段的表单
POST http://localhost:8080/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=boundary

--boundary
Content-Disposition: form-data; name="file"; filename="shakespeares.txt"

// 将上传 "input.txt "文件
< /Users/mustafaguc/Desktop/kafka-demo-content/shakespeares.txt

--boundary

4.3 步骤 3:docker化 TextData 生产者

创建 Dockerfile,将微服务打包成 Docker 镜像

FROM openjdk:21-slim
WORKDIR /app
COPY build/libs/text-producer-1.0.jar app.jar
EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

在构建 Docker 镜像之前,首先需要使用 Gradle 构建微服务:

./gradlew assemble

成功构建单个微服务 jar 文件后,就可以在项目根目录中构建 Docker 镜像:

docker build . -t text-producer

构建成功后,运行以下命令启动 Docker 镜像:

docker run -it -p 8080:8080 text-producer

运行上述命令后,应该会看到以下日志:

c.e.kafkaapp.TextProducerApplication     : Starting TextProducerApplication using Java 21.0.1 with PID 4871 (/Users/mustafaguc/projects/java/text-producer/build/classes/java/main started by mustafaguc in /Users/mustafaguc/projects/java/text-producer)
c.e.kafkaapp.TextProducerApplication     : No active profile set, falling back to 1 default profile: "default"
o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
o.apache.catalina.core.StandardService   : Starting service [Tomcat]
o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.17]
o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 307 ms
o.a.k.clients.admin.AdminClientConfig    : AdminClientConfig values:
........
o.a.k.clients.admin.AdminClientConfig    : These configurations '[sasl.jaas.config, idompotence.enabled]' were supplied but are not used yet.
o.a.kafka.common.utils.AppInfoParser     : Kafka version: 3.6.1
o.a.kafka.common.utils.AppInfoParser     : Kafka commitId: 5e3c2b738d253ff5
o.a.kafka.common.utils.AppInfoParser     : Kafka startTimeMs: 1708262321726
o.a.kafka.common.utils.AppInfoParser     : App info kafka.admin.client for adminclient-1 unregistered
o.apache.kafka.common.metrics.Metrics    : Metrics scheduler closed
o.apache.kafka.common.metrics.Metrics    : Closing reporter org.apache.kafka.common.metrics.JmxReporter
o.apache.kafka.common.metrics.Metrics    : Metrics reporters closed
o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path ''
c.e.kafkaapp.TextProducerApplication     : Started TextProducerApplication in 0.764 seconds (process running for 0.976)

目前,已经实现了生产者部分。我们将在下篇文章中介绍消费者和聚合器微服务。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论