spriongboot-kafka 防止消费速度过慢触发rebalance而导致的重复消费

报错消息:

org.apache.kafka.clients.consumer.CommitFailedException:
 Commit cannot be completed since the group has already rebalanced and assigned the partitions to another member.
 This means that the time between subsequent calls to poll() was longer than the configured max.poll.interval.ms,
 which typically implies that the poll loop is spending too much time message processing.
 You can address this either by increasing the session timeout or by reducing the maximum size of batches returned in poll() with max.poll.records
 

解决方案: 

spring-kafka消费出现异常:Commit cannot be completed since the group has already rebalanced 消费者突然hung住停止消费_蜗牛的小牛的博客-CSDN博客

析得知出现该异常是因为一次性poll拉取(默认500)消息后处理时间过长,导致两次拉取时间间隔超过了max.poll.interval.ms阈值(默认五分钟)。解决策略可以加大参数:max.poll.interval.ms或者减少一次性拉取的消息数量。 我这里是改了拉取消息数量和session.timeout.ms得以解决。
1>  spring配置如下:max-poll-records

这个参数定义了poll()方法最多可以返回多少条消息,默认值为500。注意这里的用词是"最多",也就是说如果在拉取消息的时候新消息不足500条,那有多少返回多少;如果超过500条,就只返回500。这个默认值是比较坑人的,如果你的消息处理逻辑比较重,比如需要查数据库,调用接口,甚至是复杂计算,那么你很难保证能够在5min内处理完500条消息,也就是说,如果上游真的突然大爆发生产了成千上万条消息,而平摊到每个消费者身上的消息达到了500的又无法按时消费完成的话就会触发rebalance, 然后这批消息会被分配到另一个消费者中,还是会处理不完,又会触发rebalance, 这样这批消息就永远也处理不完,而且一直在重复处理。

spring:
  kafka:
   consumer:
     max-poll-records: 200

2>  我也改了spring.kafka.properties.session.timeout.ms  

spring:
  kafka:
   properties:
    session:
      timeout:
        ms: 120000

3> kafka 配置 max.poll.interval.ms

这个参数定义了两次poll()之间的最大间隔,默认值为5分钟。如果超过这个间隔同样会触发rebalance。在多数情况下这个参数是导致rebalance消息重复的关键,即业务处理消息耗时太长。有人可能会疑惑,如果5分钟都没处理完消息那肯定时出了问题,其实不然。能否在5min内处理完还取决于你每次拉取了多少条消息,如果一次拿到了成千上万条的话,5min就够呛了。 

假设你一次poll调用返回的消息数是N,你处理每条消息的平均时间是t0,那么你需要设置max.poll.interval.ms稍稍大于N * t0以保证poll调用间隔不会超过该阈值。 

spring.kafka.properties.max.poll.interval.ms  两次poll的间隔默认5分钟
 
或者
kafka中config/server.properties  -- 10分钟
max.poll.interval.ms=600000  

4> spring配置config调整

configProps.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG,ConsumerConfig.DEFAULT_MAX_PARTITION_FETCH_BYTES*5);
        configProps.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG,Integer.parseInt(sessionTimeout));
        configProps.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG,Integer.parseInt(pollIntervalMs));


@Configuration
@EnableKafka
public class KafkaConsumerConfig {
    @Value("${spring.kafka.bootstrap-servers}")
    private String bootstrapServers;

    @Value("${spring.kafka.consumer.group-id}")
    private String groupId;

    @Value("${spring.kafka.consumer.auto-offset-reset}")
    private String autoOffset;

    @Value("${spring.kafka.properties.max.poll.interval.ms}")
    private String pollIntervalMs;

    @Value("${spring.kafka.properties.session.timeout.ms}")
    private String sessionTimeout;

    @Value("${spring.kafka.consumer.max-poll-records}")
    private String pollRecord;

    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, LogReceive> basicKafkaListenerContainerFactory() {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
        configProps.put(ConsumerConfig.GROUP_ID_CONFIG,groupId);
        configProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,autoOffset);
        configProps.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG,ConsumerConfig.DEFAULT_MAX_PARTITION_FETCH_BYTES*5);
        configProps.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG,Integer.parseInt(sessionTimeout));
        configProps.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG,Integer.parseInt(pollIntervalMs));
        configProps.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG,Integer.parseInt(pollRecord));
        ConcurrentKafkaListenerContainerFactory<String, LogReceive> factory = new ConcurrentKafkaListenerContainerFactory<>();
        ConsumerFactory<String,LogReceive> basicConsumerFactory =
                new DefaultKafkaConsumerFactory<>(configProps,
                        new StringDeserializer(),
                        new JsonDeserializer<>(LogReceive.class));
        factory.setConsumerFactory(basicConsumerFactory);

        return factory;
    }


}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值