使用SpringBoot集成Kafka

1、依赖及yml配置

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    <version>2.8.0</version>
</dependency>
<!-- 监控(可选) -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
spring:
  kafka:
    # 生产者配置
    producer:
      bootstrap-servers: kafka1:9092,kafka2:9092,kafka3:9092
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      # 重试机制
      retries: 3
      # 批量发送
      batch-size: 16384
      linger-ms: 1
    # 消费者配置
    consumer:
      bootstrap-servers: kafka1:9092,kafka2:9092,kafka3:9092
      group-id: "my-consumer-group"
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      # 自动提交偏移量(生产环境建议手动提交)
      enable-auto-commit: false
      # 从最新偏移量开始消费(根据业务调整)
      auto-offset-reset: earliest/latest
      # 并发消费者数(根据分区数调整)
      concurrency: 3
    # 监听器配置
    listener:
      # 手动提交偏移量
      ack-mode: MANUAL_IMMEDIATE
      # 消费失败重试
      retry:
        max-attempts: 3
        initial-interval: 1000
        multiplier: 2.0
        max-interval: 10000

auto-offset-reset

auto.offset.reset 参数决定了消费者组在以下场景的行为:

  • 消费者组初次启动 (无历史偏移量)。
  • 偏移量无效 (如消费者组被删除或分区数据过期)。

earliest:从头开始读,读取全量历史数据

latest:仅读最新数据

搭配enable-auto-commit: false手动提交避免消息丢失

问题 1:消息重复消费

  • 原因 :自动提交偏移量可能在消息处理前提交。
  • 解决方案 :关闭自动提交,手动提交偏移量。

问题 2:消息丢失

  • 原因 :消费者崩溃时,未提交的偏移量可能丢失。
  • 解决方案 :结合手动提交和事务(Kafka Transactions)。

2、生产者配置(事务支持)

@Configuration
public class KafkaProducerConfig {

    @Bean
    public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> config = new HashMap<>();
        config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka1:9092,kafka2:9092,kafka3:9092");
        config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        
        // 显式启用幂等性
        config.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
        
        // 固定事务 ID(示例:结合应用名和实例 ID)
        String transactionId = "tx-myapp-" + UUID.randomUUID().toString(); // 或使用固定前缀 + 实例标识
        config.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, transactionId);
        
        return new DefaultKafkaProducerFactory<>(config);
    }

    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }

    @Bean
    public KafkaTransactionManager<String, String> kafkaTransactionManager() {
        return new KafkaTransactionManager<>(producerFactory());
    }
}

3、消费者配置(手动提交+错误处理DLQ)

@Configuration
@EnableKafka
public class KafkaConsumerConfig {

    @Bean
    public ConsumerFactory<String, String> consumerFactory() {
        Map<String, Object> config = new HashMap<>();
        config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka1:9092,kafka2:9092,kafka3:9092");
        config.put(ConsumerConfig.GROUP_ID_CONFIG, "my-consumer-group");
        
        // 使用 ErrorHandlingDeserializer 包装实际反序列化器
        config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class);
        config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class);
        config.put(ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS, StringDeserializer.class);
        config.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, StringDeserializer.class);
        
        config.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        config.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
        return new DefaultKafkaConsumerFactory<>(config);
    }

@Bean
public ConcurrentKafkaListenerContainerFactory<?, ?> kafkaListenerContainerFactory(
        ConsumerFactory<Object, Object> consumerFactory,
        KafkaTemplate<Object, Object> kafkaTemplate) {

    ConcurrentKafkaListenerContainerFactory<Object, Object> factory =
        new ConcurrentKafkaListenerContainerFactory<>();
    factory.setConsumerFactory(consumerFactory);
    factory.setConcurrency(3);
    
    // 配置错误处理器(重试3次后转发到DLQ)
    factory.setErrorHandler(new DefaultErrorHandler(
        new DeadLetterPublishingRecoverer(kafkaTemplate,
            (record, exception) -> {
                // 自定义 DLQ Topic 名称(例如按原始 Topic 分类)
                return TopicPartition.of("dlq-" + record.topic());
            }),
        new FixedBackOff(1000, 3) // 重试3次,间隔1秒
    ));
    
    return factory;
}
}

DeadLetterPublishingRecoverer的作用

  • 功能
    当消息消费失败且重试次数耗尽后,DeadLetterPublishingRecoverer 会通过 KafkaTemplate 将失败消息发送到指定的 DLQ Topic。
  • DLQ Topic 命名规则
    你的配置中使用 dlq-<original-topic>,例如原始 Topic 是 user-activity,则 DLQ 是 dlq-user-activity

DefaultErrorHandler的行为

  • 重试策略
    FixedBackOff(1000, 3) 表示最多重试 3 次,间隔 1 秒。
  • 触发条件
    当消费者抛出异常且重试次数用尽时,DeadLetterPublishingRecoverer 会被调用。

DLQ生效条件

  • 确保 DLQ Topic 存在
    • Kafka 不会自动创建 DLQ Topic,需手动创建或启用自动创建
  • 禁用自动提交偏移量
  • 显式地抛出异常

4、生产者服务(事务消息)

事务与异步发送的交互原理

  1. 事务消息的缓冲机制
    当使用 @Transactional 时,KafkaTemplate 的 send() 操作会将消息暂存到事务缓冲区,而不是立即发送到 Kafka Broker。
    实际的消息发送会延迟到事务提交时(通常是在方法成功返回时)才会批量发送。
  2. 异步回调的触发时机
    即使使用了 ListenableFuture 的回调机制:
    • onSuccess/onFailure 回调会在 消息被写入事务缓冲区后立即触发
    • 而不是在消息实际发送到 Kafka Broker 之后
      这意味着回调中无法准确反映消息最终是否成功提交到 Kafka。

1、同步发送+事务

@Transactional("kafkaTxManager")
public void transactionalSend() {
    // 同步等待发送完成(实际仍会延迟到事务提交)
    SendResult<K, V> result = kafkaTemplate.send(topic, message).get(); 
    log.info("消息进入事务缓冲区, offset暂未分配");
} // 事务提交时才会真正发送

2、使用Transactional Event Listeners

(Spring Kafka 2.5+)

@Transactional("kafkaTxManager")
public void sendWithEvent() {
    kafkaTemplate.send(topic, message);
    // 事务提交后触发事件
    TransactionSynchronizationManager.registerSynchronization(
        new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                // 这里可以安全处理发送成功逻辑
            }
        });
}

3、禁用事务的自动提交

手动控制事务

@Autowired private KafkaTemplate<String, String> kafkaTemplate;

public void manualTransaction() {
    kafkaTemplate.executeInTransaction(t -> {
        ListenableFuture<SendResult<String, String>> future = t.send(topic, message);
    future.addCallback(
    	result -> log.info("Sent message=[{}] with offset=[{}]", message, result.getRecordMetadata().offset()),
   	    ex -> log.error("Failed to send message=[{}]", message, ex)); // 此时回调在事务提交后触发
        return null;
    });
}

4、取消事务

  public void send(String message) {
    CompletableFuture<SendResult<String, String>> future = kafkaTemplate.send("test", message);
    future.whenComplete((result, ex) -> {
      if (ex == null) {
        System.out.println("发送消息成功:" 
                           + result.getRecordMetadata().topic() + "-" 
                           + result.getRecordMetadata().partition() + "-" + 								result.getRecordMetadata().offset());
      } else {
        System.out.println("发送消息失败:" + ex.getMessage());
      }
    });

  }

5、消费者服务(手动提交)

@KafkaListener(topics = "my_topic", groupId = "my-consumer-group")
public void listen(ConsumerRecord<String, String> record, Acknowledgment ack) {
    try {
        // 处理消息
        System.out.println("收到消息: " + record.value());
        
        // 模拟业务异常
        if (record.value().contains("bad-data")) {
            throw new RuntimeException("模拟处理失败");
        }
        
        // 手动提交偏移量(确保是最后一行)
        ack.acknowledge();
    } catch (Exception e) {
        // 记录异常并抛出(触发重试或DLQ)
        log.error("消息处理失败: {}", record.value(), e);
        throw new RuntimeException("消息处理失败", e);
    }
}
以下是一个简单的Spring Boot集成Kafka的代码模板: 1. 在pom.xml中添加Kafka的依赖: ```xml <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>${spring.kafka.version}</version> </dependency> ``` 2. 配置Kafka的连接信息和生产者/消费者的属性: ```yaml spring: kafka: bootstrap-servers: localhost:9092 consumer: group-id: my-group auto-offset-reset: earliest enable-auto-commit: false producer: acks: all retries: 0 batch-size: 16384 buffer-memory: 33554432 ``` 3. 创建一个Kafka生产者: ```java import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; @Service public class KafkaProducer { private final KafkaTemplate<String, String> kafkaTemplate; public KafkaProducer(KafkaTemplate<String, String> kafkaTemplate) { this.kafkaTemplate = kafkaTemplate; } public void send(String topic, String message) { kafkaTemplate.send(topic, message); } } ``` 4. 创建一个Kafka消费者: ```java import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; @Component public class KafkaConsumer { @KafkaListener(topics = "my-topic") public void listen(String message) { System.out.println("Received message: " + message); } } ``` 5. 在应用程序中使用生产者和消费者: ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class MyApp { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(MyApp.class, args); KafkaProducer producer = context.getBean(KafkaProducer.class); producer.send("my-topic", "Hello, Kafka!"); KafkaConsumer consumer = context.getBean(KafkaConsumer.class); // The consumer will listen for messages on the "my-topic" topic } } ``` 这个代码模板可以帮助你快速集成Kafka到你的Spring Boot应用程序中,但请注意,这只是一个简单的示例,实际应用中可能需要更复杂的配置和逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值