如何在不手动分配分区的情况下实施完全一次Kafka Consumer

时间:2019-09-20 14:08:41

标签: apache-kafka kafka-consumer-api

我正在浏览this article,其中解释了如何通过执行以下操作来确保对消息进行一次准确的处理:

  • 启动/重新启动时从数据库读取(主题,分区,偏移量)
  • 从特定(主题,分区,偏移量)读取消息
  • 以原子方式执行以下操作:
    • 正在处理消息
    • 将数据库的偏移量作为(主题,分区,偏移量)提交

如您所见,它明确指定从哪个分区读取消息。我觉得这不是一个好主意,因为它不允许Kafka向活跃的消费者分配合理的分区份额。在轮询使用者内的kafka主题时,如果没有显式指定分区,我将无法实现类似功能的逻辑。有可能吗?

1 个答案:

答案 0 :(得分:1)

很好的分析。您有一个很好的观点,如果可能的话,您当然应该让kafka处理分配给使用者的分区。

consumer.Assign(Partition [])可以替代。当分区被撤销或分配给消费者时,kafka经纪人将通知您的消费者。例如,dotnet客户端库具有一个“ SetPartitionsRevoked”和“ SetPartitionsAssigned”处理程序,消费者可以使用该处理程序来管理其偏移量。

吊销分区时,将每个吊销的分区的上次处理偏移量保留到数据库中。分配新分区后,请从数据库中获取该分区的上一个已处理偏移量,然后使用它。

C#示例:

public class Program
{
   public void Main(string[] args)
   {

      using (
         var consumer = new ConsumerBuilder<string, string>(config)
                      .SetErrorHandler(ErrorHandler)
                      .SetPartitionsRevokedHandler(HandlePartitionsRevoked)
                      .SetPartitionsAssigned(HandlePartitionsAssigned)
                      .Build()
      )
      {
         while (true)
         {
            consumer.Consume()//.Poll()
         }
      }
   }

   public IEnumerable<TopicPartitionOffset> 
   HandlePartitionsRevoked
   (
      IConsumer<string, string> consumer, 
      List<TopicPartitionOffset> currentTopicPartitionOffsets
   )
   {
      Persist(<last processed offset for each partition in 
      'currentTopicPartitionOffsets'>);
      return tpos;
   }

   public IEnumerable<TopicPartitionOffset> HandlePartitionsAssigned
   (
      IConsumer<string, string> consumer, 
      List<TopicPartition> tps
    )
   {
      List<TopicPartitionOffset> tpos = FetchOffsetsFromDbForTopicPartitions(tps);
      return tpos
   }
}

ConsumerRebalanceListener Docs中的Java示例:

如果使用Java编写,则可以实现一个“ ConsumerRebalanceListener”接口。然后,您将接口的实现传递给consumer.Subscribe(topic,listener)方法。下面的示例是从上面链接的kafka文档中逐字获取的:

public class SaveOffsetsOnRebalance implements ConsumerRebalanceListener {
       private Consumer<?,?> consumer;

       public SaveOffsetsOnRebalance(Consumer<?,?> consumer) {
           this.consumer = consumer;
       }

       public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
           // save the offsets in an external store using some custom code not described here
           for(TopicPartition partition: partitions)
              saveOffsetInExternalStore(consumer.position(partition));
       }

       public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
           // read the offsets from an external store using some custom code not described here
           for(TopicPartition partition: partitions)
              consumer.seek(partition, readOffsetFromExternalStore(partition));
    }
}

如果我的理解是正确的,则可以这样调用Java版本:consumer.Subscribe("My topic", new SaveOffsetsOnRebalance(consumer))

有关更多信息,请参见kafka docs的“在Kafka外部存储偏移”一节。

这是这些文档的摘录,总结了如何存储分区和偏移量以进行一次处理:

  

每个记录都有其自己的偏移量,因此可以管理自己的偏移量   您只需要执行以下操作:

     
      
  • 配置enable.auto.commit = false
  •   
  • 使用每个ConsumerRecord随附的偏移量保存位置。
  •   
  • 重新启动时,使用seek(TopicPartition,long)恢复使用者的位置。
  •   
     

当分区分配也是   手动完成(这可能在搜索索引用例中   如上所述)。如果分区分配是自动完成的   需要特别注意处理分区分配的情况   更改。这可以通过提供ConsumerRebalanceListener来完成   订阅(Collection,   ConsumerRebalanceListener)和subscription(Pattern,   ConsumerRebalanceListener)。例如,当采用分区时   从消费者那里,消费者将希望为其提供补偿   通过实现分区   ConsumerRebalanceListener.onPartitionsRevoked(Collection)。什么时候   分区分配给使用者,使用者将要查看   这些新分区的偏移量并正确初始化   消费者通过实施   ConsumerRebalanceListener.onPartitionsAssigned(Collection)。

     

ConsumerRebalanceListener的另一个常用用法是刷新任何   缓存应用程序维护的移动分区   其他地方。