关于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;
    }
}
相关推荐
@淡 定2 分钟前
缓存原理详解
java·spring·缓存
北城以北88886 分钟前
RabbitMQ基础知识
spring boot·分布式·rabbitmq·intellij-idea
带刺的坐椅7 分钟前
超越 SpringBoot 4.0了吗?OpenSolon v3.8, v3.7.4, v3.6.7 发布
java·ai·springboot·web·solon·flow·mcp
就叫飞六吧7 分钟前
基于spring web实现简单分片上传demo
java·前端·spring
invicinble9 分钟前
网站设计整体思路
spring boot
apihz14 分钟前
免费手机号归属地查询API接口详细教程
android·java·运维·服务器·开发语言
程序员小假15 分钟前
学院本大二混子终于找到实习了...
java·后端
回吐泡泡oO16 分钟前
找不到rar.RarArchiveInputStream?JAVA解压RAR5的方案。
java·开发语言
搬砖快乐~17 分钟前
面经:大数据开发岗-初面 面试题(40分钟)
大数据·hadoop·spark·kafka·面试题·面经
jiayong2318 分钟前
AI应用领域编程语言选择指南:Java vs Python vs Go
java·人工智能·python