关于SpringBoot集成Kafka

关于Kafka

Apache Kafka 是一个分布式流处理平台,广泛用于构建实时数据管道和流应用。它能够处理大量的数据流,具有高吞吐量、可持久化存储、容错性和扩展性等特性。

Kafka一般用作实时数据流处理、消息队列、事件架构驱动等

Kafka的整体架构

  • ZooKeeper:

位于架构的顶部,负责管理和协调 Kafka 集群的各种元数据,包括集群配置、主题信息、分区领导者的选举等。

  • Producers (生产者):

Kafka体系中的主要角色之一,它们负责产生消息并将其发送到 Kafka 集群中的相应主题。

  • Brokers (代理/服务器) :节点,也就是具体的Kafka实例,集群的组成单元,多个 Broker 组成一个集群。每个 Broker 存储着一部分主题的分区数据。
    • Topics (主题): 主题是消息的类别,生产者将消息发布到特定的主题。一个Brokers包含多个topic。
      • Partitions (分区): 每个主题可以划分为一个或多个分区,分区是物理上存储消息的地方。一个主题可以有多个分区
        • **Leaders and Followers (领导者和跟随者):**对于每个分区,有一个 Leader 负责接受所有生产和消费请求,而 Follower 则复制 Leader (图中红色部分)的数据。
        • **Segments (段):**分区内部又细分为多个 Segment,每个 Segment 包括一个 .log 文件和其他相关文件,用于消息数据持久化。
  • Consumers (消费者):

Kafka体系中的另一个主要角色,它们订阅主题并从 Kafka 集群中拉取消息。消费者可以组织成消费者组,同一组内的消费者共享消息。

Kafka的工作流程

  • 消息发布

生产者创建消息并将它们发送到指定的主题。生产者可以选择将消息发送到特定的分区,或者让 Kafka 根据某种策略(如轮询、哈希等)自动选择分区。

  • 消息存储

当消息到达Broker时,它会被追加到指定分区的日志文件末尾。每个分区都是一个独立的日志文件,按照偏移量(Offset)进行索引,确保消息的顺序性。

  • 消息消费

消费者订阅感兴趣的主题,并从指定的分区读取消息。消费者通过维护一个偏移量来跟踪已经处理的消息,确保不会重复消费。

  • 故障恢复

如果Broker发生故障,Kafka会从其他副本中选举一个新的Leader分区来继续提供服务,确保系统的高可用性。

SpringBoot集成Kafka

引入依赖

在SpringBoot框架下,可以直接通过引入Kafka依

XML 复制代码
    <!-- Spring Kafka Starter -->
    <dependency>
        <groupId>org.springframework.kafka</groupId>
        <artifactId>spring-kafka</artifactId>
    </dependency>

yaml配置详解

关于SpringBoot中使用Kafka的配置结构如下:

bash 复制代码
spring:
  kafka:
    bootstrap-servers: localhost:9092  # Kafka整体配置:服务器地址,可以是多个以逗号分隔
    consumer: #作为消费者的具体配置
      group-id: my-group # 消费者的组ID,属于同一组的消费者会互相竞争消费消息
      # ...... 消费者其他的配置
    producer: #作为生产者的具体配置
      value-serializer: org.apache.kafka.common.serialization.StringSerializer # 值的序列化器
      #......其他控制生产者的配置

各类参数配置说明:

归属 配置项 说明 默认值 作用
全局 bootstrap-servers 指定Kafka集群的地址,可以是多个地址,用逗号分隔 - 生产者和消费者通过这些地址与Kafka集群建立连接
消费者 consumer.group-id 指定消费者的组ID - 属于同一组的消费者会互相竞争消费消息,确保每个消息只被组内的一个消费者消费
消费者 consumer.auto-offset-reset 当没有初始偏移量可用或当前偏移量不再存在时,自动重置偏移量的策略 latest 决定了在消费者组没有已知偏移量时,从哪里开始读取消息
消费者 consumer.key-deserializer 指定键的反序列化器 org.apache.kafka.common.serialization.ByteArrayDeserializer 将从Kafka接收到的字节数据转换为Java对象
消费者 consumer.value-deserializer 指定值的反序列化器 org.apache.kafka.common.serialization.ByteArrayDeserializer 将从Kafka接收到的字节数据转换为Java对象
消费者 consumer.enable-auto-commit 是否启用自动提交偏移量 true 如果启用,消费者会定期自动提交偏移量,否则需要手动提交
消费者 consumer.auto-commit-interval 自动提交偏移量的时间间隔(毫秒) 5000(5秒) 控制自动提交的频率
消费者 consumer.max-poll-records 单次轮询返回的最大记录数 500 控制每次轮询从Kafka获取的消息数量
消费者 consumer.fetch-min-bytes 消费者从Kafka获取数据的最小字节数 1 如果小于该值,Kafka不会立即返回数据,而是等待更多数据积累
消费者 consumer.fetch-max-wait 消费者从Kafka获取数据的最大等待时间(毫秒) 500 控制消费者在没有足够数据时的等待时间
消费者 consumer.session-timeout 消费者会话超时时间(毫秒) 10000(10秒) 控制消费者在未发送心跳的情况下,Kafka认为其失效的时间
生产者 producer.key-serializer 指定键的序列化器 org.apache.kafka.common.serialization.ByteArraySerializer 将Java对象转换为字节数据,以便发送到Kafka
生产者 producer.value-serializer 指定值的序列化器 org.apache.kafka.common.serialization.ByteArraySerializer 将Java对象转换为字节数据,以便发送到Kafka
生产者 producer.acks 生产者发送消息时的确认模式 1 控制消息发送的可靠性
生产者 producer.retries 生产者发送消息失败时的重试次数 0 控制消息发送失败时的重试机制
生产者 producer.batch-size 生产者批量发送消息的大小(字节) 16384(16KB) 控制消息的批量发送,提高性能
生产者 producer.linger-ms 生产者在发送消息前等待的时间(毫秒),以便收集更多的消息进行批量发送 0 控制消息的批量发送,提高性能

示例配置

bash 复制代码
spring:
  kafka:
    # 指定Kafka集群的地址,可以是多个地址,用逗号分隔
    # 生产者和消费者通过这些地址与Kafka集群建立连接
    bootstrap-servers: localhost:9092

    consumer:
      # 指定消费者的组ID
      # 属于同一组的消费者会互相竞争消费消息,确保每个消息只被组内的一个消费者消费
      group-id: my-group

      # 当没有初始偏移量可用或当前偏移量不再存在时,自动重置偏移量的策略
      # 可选值: earliest, latest, none
      # earliest: 自动重置为最早的偏移量
      # latest: 自动重置为最新的偏移量
      # none: 如果没有找到消费者组的偏移量,则抛出异常
      auto-offset-reset: earliest

      # 指定键的反序列化器
      # 将从Kafka接收到的字节数据转换为Java对象
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer

      # 指定值的反序列化器
      # 将从Kafka接收到的字节数据转换为Java对象
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer

      # 是否启用自动提交偏移量
      # 如果启用,消费者会定期自动提交偏移量,否则需要手动提交
      enable-auto-commit: true

      # 自动提交偏移量的时间间隔(毫秒)
      # 控制自动提交的频率
      auto-commit-interval: 5000

      # 单次轮询返回的最大记录数
      # 控制每次轮询从Kafka获取的消息数量
      max-poll-records: 100

      # 消费者从Kafka获取数据的最小字节数
      # 如果小于该值,Kafka不会立即返回数据,而是等待更多数据积累
      fetch-min-bytes: 1

      # 消费者从Kafka获取数据的最大等待时间(毫秒)
      # 控制消费者在没有足够数据时的等待时间
      fetch-max-wait: 500

      # 消费者会话超时时间(毫秒)
      # 控制消费者在未发送心跳的情况下,Kafka认为其失效的时间
      session-timeout: 10000

    producer:
      # 指定键的序列化器
      # 将Java对象转换为字节数据,以便发送到Kafka
      key-serializer: org.apache.kafka.common.serialization.StringSerializer

      # 指定值的序列化器
      # 将Java对象转换为字节数据,以便发送到Kafka
      value-serializer: org.apache.kafka.common.serialization.StringSerializer

      # 生产者发送消息时的确认模式
      # 可选值: 0, 1, all
      # 0: 不等待任何确认
      # 1: 等待leader节点确认
      # all: 等待所有副本节点确认
      acks: all

      # 生产者发送消息失败时的重试次数
      # 控制消息发送失败时的重试机制
      retries: 3

      # 生产者批量发送消息的大小(字节)
      # 控制消息的批量发送,提高性能
      batch-size: 16384

      # 生产者在发送消息前等待的时间(毫秒),以便收集更多的消息进行批量发送
      # 控制消息的批量发送,提高性能
      linger-ms: 1

关于消费者的 auto-commit属性:

enable-auto-commit 是Kafka消费者的一个重要配置属性,它决定了消费者是否自动提交偏移量。

如果设置为true,即自动提交:

  • 消费者会定期自动提交偏移量。
  • 提交的频率由 auto-commit-interval 配置项决定,默认为5秒。
  • 消费者在每次轮询后会检查是否需要提交偏移量,如果达到提交间隔时间,则提交当前偏移量。

设置为true的优势主要在于简化开发,如果消费者崩溃,重新启动后可以从上次提交的偏移量继续消费,避免重复消费大量消息。劣势则是自动提交的频率在处理大数据量是会有性能风险。

如果设置为false,即手动提交:

  • 消费者不会自动提交偏移量。
  • 开发者需要手动调用方法来提交偏移量(具体提交方法在下文)
  • 可以在消息处理完成后立即提交偏移量,也可以批量提交。

设置为false的优势在于更为精准的控制,能有效提升性能,开发者可以完全控制何时提交偏移量,确保消息处理的可靠性和一致性。可以根据业务需求选择同步提交或异步提交,具有更强的灵活性。劣势则是如果忘记提交偏移量,可能会导致消息重复消费或丢失。

消费与生成的使用

进行消息监听与消费

消息的监听主要是通过**@KafkaListener**注解来完成,以下是一个例子:

java 复制代码
@Service
public class KafkaConsumer {

    @Autowired
    private DataService dataService;

    @KafkaListener(topics = "my-topic", groupId = "my-group")
    public void listen(String message) {
       //如果你的方法参数是一个 String 类型,那么默认情况下,这个参数会被解析为消息的值(Value)
       log.info("Received Message: {}" , message);
       dataService.OpData(message); 
    }


    //如果你想同时获取消息的键(Key)和值(Value),可以使用 ConsumerRecord 对象。ConsumerRecord 包含了消息的所有信息,包括键、值、分区、偏移量等。
    @KafkaListener(topics = "my-topic")
    public void listen(ConsumerRecord<String, String> record) {
        log.info("Received Message Key: " + record.key());
        log.info("Received Message Value: " + record.value());
        log.info("Partition: " + record.partition());
        log.info("Offset: " + record.offset());
    }

}

关于@KafkaListener注解的参数如下:

参数 类型 说明 示例
topics String[] 指定要监听的主题列表。 @KafkaListener(topics = "my-topic")
topicPartitions TopicPartitionOffset[] 指定要监听的主题和分区。可以用于更细粒度的控制。 @KafkaListener(topicPartitions = @TopicPartition(topic = "my-topic", partitions = { "0", "1" }))
groupId String 指定消费者的组ID。如果在配置文件中已经指定了组ID,这里可以省略。 @KafkaListener(topics = "my-topic", groupId = "my-group")
containerFactory String 指定用于创建监听器容器的工厂Bean的名称。通常在配置类中定义。 @KafkaListener(topics = "my-topic", containerFactory = "kafkaListenerContainerFactory")
id String 指定监听器的唯一标识符。可以在监控和管理中使用。 @KafkaListener(topics = "my-topic", id = "myListener")
concurrency String 指定并发消费者线程的数量。 @KafkaListener(topics = "my-topic", concurrency = "3")
autoStartup boolean 指定监听器是否在应用程序启动时自动启动。 @KafkaListener(topics = "my-topic", autoStartup = "false")
properties Map<String, String> 指定额外的Kafka消费者属性。 @KafkaListener(topics = "my-topic", properties = "max.poll.interval.ms:120000")
groupIdPrefix String 指定组ID的前缀。实际的组ID将是前缀加上方法名。 @KafkaListener(topics = "my-topic", groupIdPrefix = "prefix-")
clientIdPrefix String 指定客户端ID的前缀。实际的客户端ID将是前缀加上方法名。 @KafkaListener(topics = "my-topic", clientIdPrefix = "client-")
containerProperties ContainerProperties 指定容器的属性。 @KafkaListener(topics = "my-topic", containerProperties = @ContainerProperties(ackMode = AckMode.MANUAL))
errorHandler String 指定错误处理器Bean的名称。 @KafkaListener(topics = "my-topic", errorHandler = "myErrorHandler")
replyTemplate String 指定回复模板Bean的名称。 @KafkaListener(topics = "my-topic", replyTemplate = "myReplyTemplate")
replyTopic String 指定回复主题。 @KafkaListener(topics = "my-topic", replyTopic = "reply-topic")
replyTimeout long 指定回复超时时间(毫秒)。 @KafkaListener(topics = "my-topic", replyTimeout = 5000)
replyHeaders String[] 指定要传递的回复头。 @KafkaListener(topics = "my-topic", replyHeaders = { "header1", "header2" })
groupIdIsolationLevel IsolationLevel 指定隔离级别。 @KafkaListener(topics = "my-topic", groupIdIsolationLevel = IsolationLevel.READ_COMMITTED)
containerGroup String 指定容器组。 @KafkaListener(topics = "my-topic", containerGroup = "group1")

额外说明一下containerFactory 参数,为了使用 containerFactory 参数,你需要在配置类中定义相应的工厂Bean:

java 复制代码
@Configuration
@EnableKafka
public class KafkaConfig {

    @Bean
    public ConsumerFactory<String, String> consumerFactory() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group");
        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<String, String> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
        return factory;
    }
}

基于使用containerFactory配置一般主要用于手动提交的时候,可以在方法中使用提交参数,如下:

java 复制代码
@Service
public class KafkaConsumer {

    @KafkaListener(
        topics = "my-topic",
        containerFactory = "kafkaListenerContainerFactory"
    )
   public void listen(ConsumerRecord<String, String> record, Acknowledgment acknowledgment){
        log.info("Received Message: " + record.value());
        // 处理消息
        try {
            // 模拟消息处理
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 手动提交偏移量 enable-auto-commit为false的话
        acknowledgment.acknowledge();
    }
}

消息生产

主要是基于KafkaTemplate进行消息的发送,可以根据需求,先定义生产者的工厂Bean:

java 复制代码
@Configuration
public class KafkaProducerConfig {

    @Bean
    public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        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<String, String> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }
}

封装生产者方法:

java 复制代码
@Service
public class KafkaProducer {

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    /**
     * 发送字符串消息到指定主题
     *
     * @param topic   主题名称
     * @param message 消息内容
     */
    public void sendMessage(String topic, String message) {
        kafkaTemplate.send(topic, message);
    }

    /**
     * 发送带键的字符串消息到指定主题
     *
     * @param topic   主题名称
     * @param key     消息键
     * @param message 消息内容
     */
    public void sendMessageWithKey(String topic, String key, String message) {
        kafkaTemplate.send(topic, key, message);
    }

    /**
     * 异步发送消息并处理回调
     *
     * @param topic   主题名称
     * @param message 消息内容
     * @param callback 回调函数
     */
    public void sendAsyncMessage(String topic, String message, KafkaCallback<String, String> callback) {
        kafkaTemplate.send(topic, message).addCallback(callback);
    }

    /**
     * 异步发送带键的消息并处理回调
     *
     * @param topic   主题名称
     * @param key     消息键
     * @param message 消息内容
     * @param callback 回调函数
     */
    public void sendAsyncMessageWithKey(String topic, String key, String message, KafkaCallback<String, String> callback) {
        kafkaTemplate.send(topic, key, message).addCallback(callback);
    }
}

为了支持异步发送消息并处理回调,定义回调不同的接口:

java 复制代码
public interface KafkaCallback<K, V> extends ListenableFutureCallback<SendResult<K, V>> {
    @Override
    default void onSuccess(SendResult<K, V> result) {
        //回调处理
    }

    @Override
    default void onFailure(Throwable ex) {
        //处理
    }
}

直接使用

java 复制代码
@RestController
@RequestMapping("/kafka")
public class KafkaController {

    @Autowired
    private KafkaProducerService kafkaProducerService;

    @PostMapping("/send")
    public String sendMessage(@RequestBody String message) {
        kafkaProducerService.sendMessage("my-topic", message);
        return "Message sent: " + message;
    }

    @PostMapping("/send-with-key")
    public String sendMessageWithKey(@RequestBody String message) {
        kafkaProducerService.sendMessageWithKey("my-topic", "key1", message);
        return "Message with key sent: " + message;
    }

    @PostMapping("/send-async")
    public String sendAsyncMessage(@RequestBody String message) {
        kafkaProducerService.sendAsyncMessage("my-topic", message, new KafkaCallback<String, String>());
        return "Async message sent: " + message;
    }

    @PostMapping("/send-async-with-key")
    public String sendAsyncMessageWithKey(@RequestBody String message) {
        kafkaProducerService.sendAsyncMessageWithKey("my-topic", "key1", message, new KafkaCallback<String, String>());
        return "Async message with key sent: " + message;
    }
}
相关推荐
极客先躯4 分钟前
高级java每日一道面试题-2024年11月22日-JVM篇-说说堆和栈的区别?
java·jvm··
2401_8574396932 分钟前
企业OA管理系统:Spring Boot技术应用与优化
java·spring boot·后端
2401_8574396933 分钟前
Spring Boot OA:构建企业级办公自动化平台
java·spring boot·后端
paterWang1 小时前
小程序-基于java+SpringBoot+Vue的农场管理系统设计与实现
java·spring boot·小程序
wqq_9922502771 小时前
springboot基于微信小程序的停车场管理系统
spring boot·后端·微信小程序
尘浮生1 小时前
Java项目实战II基于Java+Spring Boot+MySQL的共享汽车管理系统(源码+数据库+文档)
java·数据库·spring boot·mysql·微信小程序·小程序·汽车
雪碧聊技术1 小时前
RabbitMQ3:Java客户端快速入门
java·开发语言·rabbitmq·amqp·spring amqp·rabbittemplate
九圣残炎1 小时前
【从零开始的LeetCode-算法】3297. 统计重新排列后包含另一个字符串的子字符串数目 I
java·算法·leetcode
前期后期2 小时前
Android 工厂设计模式的使用:咖啡机,可以做拿铁,可以做美式等等。
android·java·设计模式
儿时可乖了2 小时前
优化 Spring Boot 性能
spring boot·spring