Spring Boot + Kafka 消息队列从零到落地

背景

依赖

<dependency>

<groupId>org.springframework.kafka</groupId>

<artifactId>spring-kafka</artifactId>

<version>2.8.1</version>

</dependency>

发送消息

java 复制代码
//示例:
private final KafkaTemplate<String, String> kafkaTemplate;
//参数很多可参考官网文档
kafkaTemplate.send(topic, message);

接收消息

java 复制代码
 //示例:   
 @KafkaListener(topics = PositionAnalyseMessage.TOPIC, groupId = "wetool-position-analyse")
    public void consume0(PositionAnalyseMessage message) {
        this.doConsume(message);
    }

参数解释

1.在同一个消费组中,同一条消息之后被一个消费者消费

java 复制代码
// 同组的消费者A
@KafkaListener(topics = "test-topic", groupId = "same-group")
public void consumeA(String message) { }

// 同组的消费者B
@KafkaListener(topics = "test-topic", groupId = "same-group")
public void consumeB(String message) { }

2.在不同消费组中,同一条消息会被不同组的消费者都会消费(类似发布订阅)

java 复制代码
// 订单消息处理
@KafkaListener(topics = "order-topic", groupId = "order-process")
public void processOrder(Order order) {
    // 处理订单逻辑
}

// 订单统计
@KafkaListener(topics = "order-topic", groupId = "order-statistics")
public void statisticsOrder(Order order) {
    // 统计订单数据
}

// 订单通知
@KafkaListener(topics = "order-topic", groupId = "order-notification")
public void notifyOrder(Order order) {
    // 发送订单通知
}

3.containerFactory参数的作用

在 Spring Boot 中,containerFactory 是用于自定义 Kafka 消费者监听器行为 的核心配置机制。通过定义不同的 containerFactory,可以灵活控制消费者的参数、提交策略、消息处理方式等,以满足不同场景的需求。

一、containerFactory 的核心作用

  1. 覆盖默认配置

    • 修改消费者参数(如自动提交、拉取超时、并发线程数)。

    • 指定序列化/反序列化方式(如 JSON、Avro)。

    • 配置事务支持、拦截器、错误处理器等。

  2. 区分不同消费场景

    • 为不同监听器分配独立的配置(如手动提交 vs 自动提交、单条消费 vs 批量消费)。
  3. 实现高级功能

    • 批量消费(batchListener=true)。

    • 手动提交偏移量(AckMode.MANUAL)。

    • 记录消息投递次数(deliveryAttemptHeader=true)。

    • 添加全局拦截器(日志、监控、消息过滤)。

二、containerFactory 的使用示例

示例 1:手动提交偏移量的工厂

场景 :处理支付订单,需确保业务逻辑成功后再提交偏移量,避免数据不一致。
配置

java 复制代码
@Bean
public KafkaListenerContainerFactory<?> manualCommitFactory(
    ConsumerFactory<Object, Object> consumerFactory) {

    ConcurrentKafkaListenerContainerFactory<Object, Object> factory = 
        new ConcurrentKafkaListenerContainerFactory<>();
    
    // 关闭自动提交
    Map<String, Object> props = new HashMap<>(consumerFactory.getConfigurationProperties());
    props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
    factory.setConsumerFactory(new DefaultKafkaConsumerFactory<>(props));
    
    // 手动立即提交模式
    factory.getContainerProperties().setAckMode(AckMode.MANUAL_IMMEDIATE);
    
    return factory;
}

使用

java 复制代码
@KafkaListener(
    topics = "payment-orders",
    groupId = "payment-service",
    containerFactory = "manualCommitFactory" // 指定手动提交工厂
)
public void handlePayment(Order order, Acknowledgment ack) {
    paymentService.process(order);
    ack.acknowledge(); // 业务成功后提交
}

示例 2:批量消费的工厂

场景 :日志聚合服务,需批量消费消息后写入 Elasticsearch。
配置

java 复制代码
@Bean
public KafkaListenerContainerFactory<?> batchListenerFactory(
    ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
    ConsumerFactory<Object, Object> consumerFactory) {

    ConcurrentKafkaListenerContainerFactory<Object, Object> factory = 
        new ConcurrentKafkaListenerContainerFactory<>();
    
    configurer.configure(factory, consumerFactory);
    factory.setBatchListener(true); // 启用批量模式
    factory.setMessageConverter(new BatchMessagingMessageConverter()); // 批量消息转换
    
    return factory;
}

使用

java 复制代码
@KafkaListener(
    topics = "app-logs",
    containerFactory = "batchListenerFactory" // 指定批量消费工厂
)
public void processLogs(List<LogEntry> logs) {
    elasticsearchService.bulkIndex(logs); // 批量写入ES
}

示例 3:带拦截器的工厂(记录消息处理耗时)

场景 :监控消息处理性能,统计平均耗时。
配置

java 复制代码
@Bean
public KafkaListenerContainerFactory<?> monitoredFactory(
    ConsumerFactory<Object, Object> consumerFactory) {

    ConcurrentKafkaListenerContainerFactory<Object, Object> factory = 
        new ConcurrentKafkaListenerContainerFactory<>();
    
    factory.setConsumerFactory(consumerFactory);
    
    // 添加耗时记录拦截器
    factory.setRecordInterceptor(new RecordInterceptor<>() {
        @Override
        public ConsumerRecord<?, ?> intercept(ConsumerRecord<?, ?> record) {
            long startTime = System.currentTimeMillis();
            // 处理前记录时间
            return record;
        }

        @Override
        public void success(ConsumerRecord<?, ?> record, Object result) {
            long duration = System.currentTimeMillis() - startTime;
            log.info("消息处理耗时: {}ms", duration);
        }
    });
    
    return factory;
}

使用

java 复制代码
@KafkaListener(
    topics = "user-events",
    containerFactory = "monitoredFactory" // 使用带拦截器的工厂
)
public void handleUserEvent(UserEvent event) {
    // 处理用户事件
}
场景 关键配置 适用工厂
手动提交偏移量 enable.auto.commit=false + AckMode.MANUAL_IMMEDIATE manualCommitFactory
批量消费 batchListener=true + max.poll.records=1000 batchListenerFactory
事务消息 producer.transaction-id-prefix=tx- + @Transactional 需事务支持的工厂
消息重试与死信队列 RetryTemplate + DeadLetterPublishingRecoverer 配置重试策略的工厂
安全认证(SSL/SASL) security.protocol=SSL + sasl.jaas.config 带安全配置的工厂

4.concurrency的作用

java 复制代码
@KafkaListener(topics = "mysql-binlog-content", groupId = "marketing-mysql-binlog-content", concurrency = "1")
    public void consumeContentBinlog(@Header("tableName") String table, Binlog binlog) {
        BinlogHandleExecutors.handleBinlog(CONTENT,table, binlog);
    }

concurrency 参数用于控制 Kafka 消费者的并发度,即同时处理消息的线程数量
在这个例子中 concurrency = "1" 表示:

  1. 只使用一个线程来消费 Kafka 消息
  1. 消息会按顺序被处理,不会出现并发处理的情况
  1. 保证了消息处理的顺序性

具体来说:

  • 当 concurrency = "1" 时,消息会严格按照 Kafka 分区中的顺序被处理
  • 如果设置 concurrency > 1,则会创建多个消费者线程并行处理消息,这可能会提高吞吐量,但会失去消息处理的顺序性
  • 对于需要保证消息处理顺序的场景(比如数据库 binlog 处理),通常建议使用 concurrency = "1"

在这个代码中,由于处理的是数据库 binlog 数据,需要保证数据变更的顺序性,所以设置为 1 是合理的。这样可以确保 binlog 事件按照发生的顺序被处理,避免数据不一致的问题。

例如:想象你有一个快餐店,concurrency 就相当于收银台的数量:

  1. concurrency = "1" 的情况:
  • 就像只有一个收银台
  • 顾客必须排成一队,一个接一个地结账
  • 虽然处理速度可能较慢,但能保证每个顾客都按顺序被服务
  • 不会出现顾客 A 比顾客 B 先来,但顾客 B 却先结完账的情况
  1. concurrency > 1 的情况:
  • 就像有多个收银台同时工作
  • 顾客可以分散到不同的收银台结账
  • 处理速度更快,但可能会打乱顾客的排队顺序
  • 可能出现顾客 A 比顾客 B 先来,但顾客 B 先结完账的情况

回到代码中的场景:

  • 这个代码处理的是数据库的 binlog(数据库变更记录)
  • 就像快餐店的顾客一样,这些变更记录是有顺序的
  • 比如:先修改了用户 A 的余额,再修改了用户 B 的余额
  • 如果使用 concurrency > 1,可能会先处理用户 B 的修改,再处理用户 A 的修改
  • 这就会导致数据不一致,所以这里使用 concurrency = "1" 来确保变更按顺序处理

这就是为什么在处理数据库变更记录时,通常要使用 concurrency = "1" 来保证数据的一致性。

相关推荐
高山流水&上善10 分钟前
医药档案区块链系统
java·springboot
南汐以墨37 分钟前
探秘JVM内部
java·jvm
Craaaayon39 分钟前
Java八股文-List集合
java·开发语言·数据结构·list
信徒_1 小时前
Spring 怎么解决循环依赖问题?
java·后端·spring
2301_794461571 小时前
多线程编程中的锁策略
java·开发语言
老华带你飞1 小时前
木里风景文化|基于Java+vue的木里风景文化管理平台的设计与实现(源码+数据库+文档)
java·数据库·vue.js·毕业设计·论文·风景·木里风景文化管理平台
SofterICer1 小时前
Eclipse Leshan 常见问题解答 (FAQ) 笔记
java·笔记·eclipse
liang89991 小时前
Shiro学习(四):Shiro对Session的处理和缓存
java·学习·缓存
苏格拉没有底_coder2 小时前
【Easylive】saveVideoInfo 方法详细解析
java
小杨4042 小时前
springboot框架项目实践应用十五(扩展sentinel区分来源)
spring boot·后端·spring cloud