卡夫卡消费者。 commitSync vs commitAsync

时间:2017-10-03 13:47:22

标签: java apache-kafka offset kafka-consumer-api

https://www.safaribooksonline.com/library/view/kafka-the-definitive/9781491936153/ch04.html#callout_kafka_consumers__reading_data_from_kafka_CO2-1

的引用
  

缺点是commitSync()会重试提交,直到它   要么成功要么遇到不可重试的失败,commitAsync()   不会重试。

这句话对我来说并不清楚。我想消费者会向代理发送提交请求,如果代理在某些超时内没有响应,则意味着提交失败。我错了吗?

您能详细说明commitSynccommitAsync的区别吗? 另外,请提供我更喜欢哪种提交类型的用例。

4 个答案:

答案 0 :(得分:6)

正如API文档中所述:

  

这是一个同步提交,并且会阻塞,直到提交成功或遇到不可恢复的错误(在这种情况下会将其抛给调用者)。

这意味着,commitSync是一种阻止方法。调用它会阻塞你的线程,直到它成功或失败。

例如,

while (true) {
    ConsumerRecords<String, String> records = consumer.poll(100);
    for (ConsumerRecord<String, String> record : records) {
        System.out.printf("offset = %d, key = %s, value = %s", record.offset(), record.key(), record.value());
        consumer.commitSync();
    }
}

对于for循环中的每次迭代,只有在consumer.commitSync()成功返回或中断抛出异常后,您的代码才会移动到下一次迭代。

  

这是一个异步调用,不会阻止。遇到的任何错误都会传递给回调(如果提供)或被丢弃。

这意味着,commitAsync是一种非阻塞方法。调用它不会阻止你的线程。相反,它将继续处理以下指令,无论它最终是成功还是失败。

例如,与前面的示例类似,但在这里我们使用commitAsync

while (true) {
    ConsumerRecords<String, String> records = consumer.poll(100);
    for (ConsumerRecord<String, String> record : records) {
        System.out.printf("offset = %d, key = %s, value = %s", record.offset(), record.key(), record.value());
        consumer.commitAsync(callback);
    }
}

对于for循环中的每次迭代,无论最终consumer.commitAsync()会发生什么,您的代码都将转移到下一次迭代。并且,提交的结果将由您定义的回调函数处理。

权衡:延迟与数据一致性

  • 如果您必须确保数据一致性,请选择commitSync(),因为它会确保在执行任何进一步操作之前,您将知道偏移提交是成功还是失败。但由于它是同步和阻塞,您将花费更多时间等待提交完成,这会导致高延迟。
  • 如果您确定某些数据不一致并希望延迟较低,请选择commitAsync(),因为它不会等待完成。相反,它将稍后发出提交请求并处理来自Kafka的响应(成功或失败),同时,您的代码将继续执行。

一般来说,实际行为将取决于您的实际代码以及您调用方法的位置。

答案 1 :(得分:1)

commitSync和commitAsync都使用kafka偏移量管理功能,两者都有缺点。 如果消息处理成功并且提交偏移失败(不是原子的),并且同时发生分区重新平衡,那么您处理过的消息将被其他使用者再次处理(重复处理)。如果您可以处理重复的消息,则可以使用commitAsync(因为它不会阻塞并提供低延迟,并且它提供较高的提交时间,因此您应该可以)。否则,请使用自定义偏移管理,该管理在处理和更新偏移(使用外部偏移存储)时要照顾原子性

答案 2 :(得分:1)

使用commitAsync()进行严格重试处理

在“ Kafka-权威指南”一书中,有关于如何减轻由于异步提交而导致提交较低偏移量的潜在问题的提示:

重试异步提交:获得异步重试正确的提交顺序的一种简单模式是使用单调递增的序列号。每次提交时都增加序列号,并在提交时将序列号添加到commitAsync回调中。当您准备发送重试时,请检查回调获得的提交序列号是否与实例变量相等;如果是,则没有较新的提交,可以重试。如果实例序列号更高,请不要重试,因为已经发送了新的提交。

以下代码描述了一种可能的解决方案:

import java.util._
import java.time.Duration
import org.apache.kafka.clients.consumer.{ConsumerConfig, ConsumerRecord, KafkaConsumer, OffsetAndMetadata, OffsetCommitCallback}
import org.apache.kafka.common.{KafkaException, TopicPartition}
import collection.JavaConverters._

object AsyncCommitWithCallback extends App {

  // define topic
  val topic = "myOutputTopic"

  // set properties
  val props = new Properties()
  props.put(ConsumerConfig.GROUP_ID_CONFIG, "AsyncCommitter")
  props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092")
  // [set more properties...]
  

  // create KafkaConsumer and subscribe
  val consumer = new KafkaConsumer[String, String](props)
  consumer.subscribe(List(topic).asJavaCollection)

  // initialize global counter
  val atomicLong = new AtomicLong(0)

  // consume message
  try {
    while(true) {
      val records = consumer.poll(Duration.ofMillis(1)).asScala

      if(records.nonEmpty) {
        for (data <- records) {
          // do something with the records
        }
        consumer.commitAsync(new KeepOrderAsyncCommit)
      }

    }
  } catch {
    case ex: KafkaException => ex.printStackTrace()
  } finally {
    consumer.commitSync()
    consumer.close()
  }


  class KeepOrderAsyncCommit extends OffsetCommitCallback {
    // keeping position of this callback instance
    val position = atomicLong.incrementAndGet()

    override def onComplete(offsets: util.Map[TopicPartition, OffsetAndMetadata], exception: Exception): Unit = {
      // retrying only if no other commit incremented the global counter
      if(exception != null){
        if(position == atomicLong.get) {
          consumer.commitAsync(this)
        }
      }
    }
  }

}

答案 3 :(得分:0)

commitAync不会重试,因为如果重试会造成混乱。

想象一下,您正在尝试提交偏移量20(异步),但没有提交(失败),然后下一个轮询块尝试提交偏移量40(异步),并且成功。

现在,提交偏移量20仍在等待提交,如果它重新绑定并成功执行,将会造成混乱。

混乱的是,提交的偏移量应该是40而不是20。