项目场景:

最近项目上遇到了kafka的listerner方法经常被重复执行的问题。

场景大概是这样的:一个kafka producer向kafka中发送数据,然后consumer消费这个数据,并发送一些邮件。


问题描述:

最近项目上的邮件经常会发送重复发送两份,本以为是偶发的事件,后来发现越来越频繁,甚至会重复发送三四风,故决定去查一查原因。

邮件发送的逻辑是从kafka中接收数据,然后整合之后再发送,因为kafka中数据量太大,消费次数很多不太好查,所以决定再本地先复现。

在本地的环境中,观察日志,发现在每次发送重复邮件的时候,kafka的日志中会加着这样一行:

Offset commit failed on partition xxx-report-0 at offset 47: The coordinator is not aware of this member.

意思是offset提交失败,协调器不知道这个成员。


原因分析:

经过分析,是offset提交失败后,offset没有变化,kafka仍然认为该条消息没有被消费,所以listerner还会接收到该条消息。


解决方案:

经过研读kafka的文档,发现问题出现在自动提交offset上了,如果配置文件中设置enable-auto-commit为true的话,sping-kafka会在后续自动执行consumer.commitSync();

故需要把offset的commit设置为手动执行,需要在配置文件中设置这两行:

spring.kafka.consumer.enable-auto-commit=false
spring.kafka.listener.ack-mode=MANUAL_IMMEDIATE

将enable-auto-commit设置为false,表示不会自动提交offset

ackMode是个枚举类型:

  • RECORD
    每处理一条commit一次
  • BATCH(默认)
    每次poll的时候批量提交一次,频率取决于每次poll的调用频率
  • TIME 
    每次间隔ackTime的时间去commit
  • COUNT 
    累积达到ackCount次的ack去commit
  • COUNT_TIME
    ackTime或ackCount哪个条件先满足,就commit
  • MANUAL
    listener负责ack,但是背后也是批量上去
  • MANUAL_IMMEDIATE
    listner负责ack,每调用一次,就立即commit

 

在代码中修改:

 @KafkaListener(
      topics = {"report"},
      errorHandler = "reportErrorHandler")
  public void listen(ConsumerRecord<?, ?> record, Acknowledgment ack) {
    Optional<?> kafkaMessage = Optional.ofNullable(record.value());
    log.info("get a message from kafka: topic : {}, offset : {}", record.topic(), record.offset());
    // do something
    ack.acknowledge();
  }

最下面的ack.acknowledge()是表示手动提交这个offset,经此改动,kafka的数据就没有被消费多次了。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐