kafka springboot 集成批量消费

前言


由于 Kafka 的写性能非常高,因此项目经常会碰到 Kafka 消息队列拥堵的情况。遇到这种情况,我们可以通过并发消费、批量消费的方法进行解决。

一、新建一个maven工程,添加kafka依赖

<dependency>
        <groupId>org.springframework.kafka</groupId>
        <artifactId>spring-kafka</artifactId>
         <version>2.5.4.RELEASE</version>
</dependency>


二、yaml配置文件


 

spring:
 
  kafka:
    bootstrap-servers: 127.0.0.1:9002
    producer:
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    consumer:
      group-id: test-consumer-group
      # 当 Broker 端没有 offset(如第一次消费或 offset 超过7天过期)时如何初始化 offset,当收到 OFFSET_OUT_OF_RANGE 错误时,如何重置 Offset
      # earliest:表示自动重置到 partition 的最小 offset
      # latest:默认为 latest,表示自动重置到 partition 的最大 offset
      # none:不自动进行 offset 重置,抛
      auto-offset-reset: latest
      # 是否在消费消息后将 offset 同步到 Broker,当 Consumer 失败后就能从 Broker 获取最新的 offset
      enable-auto-commit: false
      ## 当 auto.commit.enable=true 时,自动提交 Offset 的时间间隔,建议设置至少1000
      auto-commit-interval: 2000
      max-poll-records: 30
      heartbeat-interval: 3000
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      properties:
        # 使用 Kafka 消费分组机制时,消费者超时时间。当 Broker 在该时间内没有收到消费者的心跳时,认为该消费者故障失败,Broker 发起重新 Rebalance 过程。目前该值的配置必须在 Broker 配置group.min.session.timeout.ms=6000和group.max.session.timeout.ms=300000 之间
        session.timeout.ms: 60000
        # 使用 Kafka 消费分组机制时,消费者发送心跳的间隔。这个值必须小于 session.timeout.ms,一般小于它的三分之一
        heartbeat.interval.ms: 3000
        # 使用 Kafka 消费分组机制时,再次调用 poll 允许的最大间隔。如果在该时间内没有再次调用 poll,则认为该消费者已经失败,Broker 会重新发起 Rebalance 把分配给它的 partition 分配给其他消费者
        max.poll.interval.ms: 300000
        request.timeout.ms: 600000
    listener:
      # 在侦听器容器中运行的线程数。
      concurrency: 2
      type: batch
      max-poll-records: 50
      #当 auto.commit.enable 设置为false时,表示kafak的offset由customer手动维护,
      #spring-kafka提供了通过ackMode的值表示不同的手动提交方式
      #手动调用Acknowledgment.acknowledge()后立即提交
      ack-mode: manual_immediate
      # 消费者监听的topic不存在时,项目会报错,设置为false
      missing-topics-fatal: false


三、消息消费


手动提交非批量消费


  String 类型接入
 @KafkaListener(topics = {"test-topic"}, groupId = "test-consumer-group")
    public void onMessage(String message, Consumer consumer) {
        System.out.println("接收到的消息:" + message);
        consumer.commitSync();
    }

使用注解方式获取消息头、消息体
         /**
         * 处理消息
         */
        @KafkaListener(topics = "test-topic", groupId = "test-consumer-group")
        public void onMessage(@Payload String message,
                              @Header(KafkaHeaders.RECEIVED_TOPIC) String topic,
                              @Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition,
                              @Header(name = KafkaHeaders.RECEIVED_MESSAGE_KEY, required = false) String key,
                              @Header(KafkaHeaders.RECEIVED_TIMESTAMP) long ts,
                              Acknowledgment ack) {
 
            try {
                
                ack.acknowledge();
                log.info("Consumer>>>>>>>>>>>>>end");
            } catch (Exception e) {
                log.error("Consumer.onMessage#error . message={}", message, e);
                throw new BizException("事件消息消费失败", e);
            }
        } 

手动提交批量消费


想要批量消费,首先要开启批量消费,通过listener.type属性设置为batch即可开启,看下代码吧:

spring:
  kafka:
    consumer:
      group-id: test-consumer-group
      bootstrap-servers: 127.0.0.1:9092
      max-poll-records: 50 # 一次 poll 最多返回的记录数
    listener:
      type: batch # 开启批量消费

如上设置了启用批量消费和批量消费每次最多消费记录数。这里设置 max-poll-records是50,并不是说如果没有达到50条消息,我们就一直等待。而是说一次poll最多返回的记录数为50

ConsumerRecord类接收

    /**
     * kafka的批量消费监听器
     */
    @KafkaListener(topics = "test-topic", groupId = "test-consumer-group")
    public void onMessage(List<ConsumerRecord<String, String>> records, Consumer consumer) {
        try {
            log.info("Consumer.batch#size={}", records == null ? 0 : records.size());
 
            if (CollectionUtil.isEmpty(records)) {
                //分别是commitSync(同步提交)和commitAsync(异步提交)
                consumer.commitSync();
                return;
            }
 
            for (ConsumerRecord<String, String> record : records) {
                String message = record.value();
 
                if (StringUtils.isBlank(message)) {
                    continue;
                }
 
               //处理业务数据
               //doBuiness();
            }
 
            consumer.commitSync();
            log.info("Consumer>>>>>>>>>>>>>end");
        } catch (Exception e) {
            log.error("Consumer.onMessage#error .", e);
            throw new BizException("事件消息消费失败", e);
        }
    }

String类接收

 @KafkaListener(topics = {"test-topic"}, groupId = "test-consumer-group")
    public void onMessage(List<String> message, Consumer consumer) {
        System.out.println("接收到的消息:" + message);
        consumer.commitSync();
    }

使用注解方式获取消息头、消息体,则也是使用 List 来接收:

@Component
public class KafkaConsumer {
    // 消费监听
    @KafkaListener(topics = {"test-topic"})
    public void listen2(@Payload List<String> data,
                        @Header(KafkaHeaders.RECEIVED_TOPIC) List<String> topics,
                        @Header(KafkaHeaders.RECEIVED_PARTITION_ID) List<Integer> partitions,
                        @Header(KafkaHeaders.RECEIVED_MESSAGE_KEY) List<String> keys,
                        @Header(KafkaHeaders.RECEIVED_TIMESTAMP) List<Long> tss) {
        System.out.println("收到"+ data.size() + "条消息:");
        System.out.println(data);
        System.out.println(topics);
        System.out.println(partitions);
        System.out.println(keys);
        System.out.println(tss);
    }
}


并发消费 


并发消费,为了加快消费,我们可以提高并发数,比如下面配置我们将并发设置为 3。注意:并发量根据实际分区数决定,必须小于等于分区数,否则会有线程一直处于空闲状态

spring:
  kafka:
    consumer:
      group-id: test-consumer-group
      bootstrap-servers: 127.0.0.1:9092
      max-poll-records: 50 # 一次 poll 最多返回的记录数
    listener:
      type: batch # 开启批量监听
      concurrency: 3 # 设置并发数


我们设置concurrency为3,也就是将会启动3条线程进行监听,而要监听的topic有5个partition,意味着将有2条线程都是分配到2个partition,还有1条线程分配到1个partition

配置类方式


通过自定义配置类的方式也是可以的,但是相对yml配置来说还是有点麻烦的

/**
 * 消费者配置
 */
@Configuration
public class KafkaConsumerConfig {
 
    /**
     * 消费者配置
     * @return
     */
    public Map<String,Object> consumerConfigs(){
        Map<String,Object> props = new HashMap<>();
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "test-consumer-group");
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9002");
        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 50);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        return props;
    }
 
    @Bean
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, Object>> batchFactory() {
        ConcurrentKafkaListenerContainerFactory<String, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(new DefaultKafkaConsumerFactory<>(consumerConfigs()));
        //并发数量
        factory.setConcurrency(3);
        //开启批量监听
        factory.setBatchListener(true);
        return factory;
    }
}

同时监听器通过@KafkaListener注解的containerFactory 配置指定批量消费的工厂即可,如下:

@KafkaListener(topics = {"test-topic"},containerFactory = "batchFactory")
public void consumer(List<String> message){
	System.out.println("接收到的消息:" + message);
}


四、Kafka参数调优


一、Consumer参数说明

1、enable.auto.commit
该属性指定了消费者是否自动提交偏移量,默认值是true。
为了尽量避免出现重复数据(假如,某个消费者poll消息后,应用正在处理消息,在3秒后kafka进行了重平衡,那么由于没有更新位移导致重平衡后这部分消息重复消费)和数据丢失,可以把它设为 false,由自己控制何时提交偏移量。
如果把它设为true,还可以通过配置 auto.commit.interval.ms 属性来控制提交的频率。

2、auto.commit.interval.ms
自动提交间隔。范围:[0,Integer.MAX],默认值是 5000 (5 s)

3、手动提交:commitSync/commitAsync
手动提交offset的方法有两种:分别是commitSync(同步提交)和commitAsync(异步提交)。

相同点:都会将本次poll的一批数据最大的偏移量提交。
不同点:commitSync会阻塞当前线程,一直到提交成功,并且会自动失败重试(由不可控因素导致,也会出现提交失败);而commitAsync则没有失败重试机制,故有可能提交失败,导致重复消费。

4、max.poll.records
Consumer每次调用poll()时取到的records的最大数。


二、Kafka消息积压、消费能力不足怎么解决?

如果是Kafka消费能力不足,则可以考虑增加Topic的分区数,同时相应的增加消费者实例,消费者数=分区数(二者缺一不可)。
如果是下游的数据处理不及时,则可以提高每批次拉取的数量,通过max.poll.records这个参数可以调整。
单个消费者实例的消费能力提升,可以用多线程/线程池的方式并发消费提高单机的消费能力。


三、Kafka消费者如何进行流控?

将自动提交改成手动提交(enable.auto.commit=false),每次消费完再手动异步提交offset,之后消费者再去Broker拉取新消息,这样可以做到按照消费能力拉取消息,减轻消费者的压力。
 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Boot Kafka 批量消费是指通过 Spring Boot 框架集成 Kafka,实现一次性消费多条消息的功能。在 Kafka 中,批量消费可以提高消费效率,减少网络开销,提高系统的吞吐量。Spring Boot Kafka 批量消费可以通过配置 Kafka 消费者的批量拉取大小和批量处理大小来实现。同时,还可以使用 Kafka批量消费器来实现批量消费。 ### 回答2: Spring Boot是一款非常流行的Java框架,其中集成Kafka,支持快速搭建Kafka生产者和消费者应用。而在Kafka消费者应用中,有时会需要批量消费消息,以提高性能。 批量消费是指一次性从Kafka服务器获取多个消息,然后一次性处理它们,而不是逐个处理。这种方式可以减少网络传输和处理的时间,提高处理效率,特别是在大数据量的场景下非常有用。 Spring Boot提供了多种方式来实现Kafka批量消费。其中一种方式是通过@EnableKafka注解来启用Kafka消费者,然后手动创建一个ConcurrentKafkaListenerContainerFactory,通过该工厂类来设置属性,如批量消费配置。 例如: ``` @Configuration @EnableKafka public class KafkaConfig { @Bean public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(consumerFactory()); //设置批量消费 factory.setBatchListener(true); return factory; } @Bean public ConsumerFactory<String, String> consumerFactory() { return new DefaultKafkaConsumerFactory<>(consumerConfigs()); } @Bean public Map<String, Object> consumerConfigs() { Map<String, Object> propsMap = new HashMap<>(); propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "100"); propsMap.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "15000"); propsMap.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); propsMap.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); propsMap.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, "10"); //设置每次批量获取的消息数量 return propsMap; } @Bean public Listener listener() { return new Listener(); } } ``` 以上配置已经开启了批量消费模式。在Listener类中,只需要添加@KafkaListener注解即可实现批量消费: ``` @Component public class Listener { @KafkaListener(topics = "test", containerFactory="kafkaListenerContainerFactory") public void batchListener(List<String> data) { for(String d : data) { System.out.println(d); } } } ``` 上述batchListener方法的参数列表类型为List<String>,因此Spring Boot自动将多条消息打包成list传递到batchListener方法中,实现了批量消费。 除了通过ConcurrentKafkaListenerContainerFactory手动设置批量消费,还可以通过直接定义@KafkaListener相关参数来实现: ``` @KafkaListener( topics = "test", groupId = "foo", containerFactory = "kafkaListenerContainerFactory", concurrency = "3", //设置并发处理的线程数 autoStartup = "false") public void batchListener(List<String> data) { for(String d : data) { System.out.println(d); } } ``` 总结一下,Spring Boot集成Kafka批量消费主要有两种实现方式:手动配置ConcurrentKafkaListenerContainerFactory或直接在@KafkaListener注解中设置参数。通过这种方式,能够提高消费者处理效率,适用于大数据量的场景。 ### 回答3: Spring Boot是一个轻量级的Java框架,它提供了丰富的功能和易于使用的编程模型,使得开发者可以快速构建、部署和运行应用程序。Kafka则是一个开源的分布式消息系统,它提供了高效、可靠和可扩展的消息传递机制,可以帮助开发者构建大规模的实时数据处理和消息系统。 在使用Spring Boot和Kafka进行消息处理时,很多时候需要处理大量的批量数据,例如从数据库中读取数据并批量写入到Kafka中。这时候,如何进行批量消费就成为了一个非常重要的问题。 针对这个问题,Spring Boot和Kafka提供了多种解决方案,主要包括以下几种: 1. 手动提交offset:通过手动控制offset的提交,可以实现批量消费。当处理完一批消息后,手动将offset提交到Kafka中,下次再从提交的offset开始继续消费下一批消息即可。这种方式可以提高消费的效率和吞吐量。需要注意的是,如果在消费过程中出现异常或者程序挂掉,需要通过重新启动程序并从上次提交的offset开始重新消费消息。 2. 使用BatchListener:BatchListener是Spring Kafka提供的一个可以实现批量消费的特性。通过在注解中设置batchSize参数,即可指定每一批次需要处理的消息数量。当消息数量达到batchSize时,Spring Kafka会自动调用一次BatchListener进行批量消费。需要注意的是,如果在生产环境中,需要适当地调整batchSize的大小,避免因批量消息过大导致程序内存溢出等问题。 3. 使用Kafka Consumer API:如果需要对批量消费的逻辑和流程进行更加灵活的控制,可以直接使用Kafka Consumer API。通过在Kafka Consumer API中使用poll()方法,可以实现按照批量方式获取消息。当消息数量达到一定阈值时,就可以进行批量处理。需要注意的是,使用Kafka Consumer API需要自己控制offset的提交和消费异常的处理等问题,相对比较复杂。 综上所述,Spring Boot和Kafka提供了多种实现批量消费的解决方案,选择合适的方式可以提高消息处理的效率和稳定性。需要根据实际情况进行选择和调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值