Apache Kafka:正好在0.10版本中

时间:2016-08-10 21:59:34

标签: apache-kafka kafka-consumer-api

为了实现Kafka消费者对消息的一次性处理,我一次只提交一条消息,如下所示

public void commitOneRecordConsumer(long seconds) {
        KafkaConsumer<String, String> consumer = consumerConfigFactory.getConsumerConfig();

        try {

            while (running) {
                ConsumerRecords<String, String> records = consumer.poll(1000);
                try {
                    for (ConsumerRecord<String, String> record : records) {

                        processingService.process(record);

                        consumer.commitSync(Collections.singletonMap(new TopicPartition(record.topic(),record.partition()), new OffsetAndMetadata(record.offset() + 1)));

                        System.out.println("Committed Offset" + ": " + record.offset());

                    }
                } catch (CommitFailedException e) {
                    // application specific failure handling
                }
            }
        } finally {
            consumer.close();
        }
    }

上面的代码将消息处理异步委托给下面的另一个类。

@Service
public class ProcessingService {

    @Async
    public void process(ConsumerRecord<String, String> record) throws InterruptedException {
        Thread.sleep(5000L);
        Map<String, Object> map = new HashMap<>();
        map.put("partition", record.partition());
        map.put("offset", record.offset());
        map.put("value", record.value());
        System.out.println("Processed" + ": " + map);
    }

}

但是,这仍然不能保证一次交付,因为如果处理失败,它仍然可以提交其他消息,而且以前的消息永远不会被处理和提交,我的选择是什么?

3 个答案:

答案 0 :(得分:8)

0.10.2及更早版本的原始答案(适用于0.11及更高版本,请参阅答案)

目前,Kafka无法提供开箱即用的一次性处理。如果在成功处理消息后提交消息,则可以进行至少一次处理,或者如果在开始处理之前直接在poll()之后提交消息,则可以进行至少一次处理。

(另见{em>&#34;交付担保&#34; <{em>在http://docs.confluent.io/3.0.0/clients/consumer.html#synchronous-commits中)

然而,至少一次保证是足够好的&#34;如果您的处理是幂等的,即即使您处理两次记录,最终结果也是相同的。幂等处理的示例是将消息添加到键值存储。即使您添加相同的记录两次,第二个插入只会替换第一个当前键值对,而KV存储仍然会包含正确的数据。

  

在上面的示例代码中,您更新了HashMap,这将是一个幂等操作。即使您在失败的情况下可能具有不一致的状态,例如在崩溃之前仅执行了两次put调用。但是,这种不一致状态将在再次处理同一记录时得到修复。

     

println()的调用并不是幂等的,因为这是一个带有&#34;副作用的操作&#34;。但我想打印仅用于调试目的。

作为替代方案,您需要在用户代码中实现事务语义,这需要&#34;撤消&#34; (部分执行)失败时的操作。一般来说,这是一个难题。

Apache Kafka 0.11+的更新(对于0.11之前的版本,请参见上面的答案)

从0.11开始,Apache Kafka支持使用Kafka Streams进行幂等生成器,事务生成器和一次性处理。它还向使用者添加"read_committed"模式,仅读取已提交的消息(以及删除/过滤已中止的消息)。

答案 1 :(得分:2)

答案 2 :(得分:1)

我认为只需使用kafka 0.10.x本身就可以实现处理。但是有一些问题。我从this书中分享了高层次的想法。相关内容可在部分中找到: Seek and Exactly Once Processing第4章: Kafka Consumers - Reading Data from Kafka中。您可以使用(免费)safaribooksonline帐户查看该图书的内容,也可以在帐户结束后购买,也可以从其他来源获取,我们不会谈论。

想法:

考虑这种常见情况:您的应用程序从Kafka读取事件,处理数据,然后将结果存储在数据库中。假设我们真的不想丢失任何数据,也不想将相同的结果存储在数据库中两次。

如果有办法将记录和偏移量存储在一个原子动作中,那么它是可行的。记录和偏移都已提交,或者都没有提交。 为此,我们需要在一个事务中将记录和偏移量写入数据库。然后我们就会知道我们已经完成了记录并且提交了偏移量,或者我们没有,并且记录将被重新处理。

现在唯一的问题是:如果记录存储在数据库而不是Kafka中,我们的消费者在分配分区时如何知道从何处开始阅读?这正是seek()可以使用的内容。当消费者启动或分配新分区时,它可以在数据库中查找偏移量并seek()到该位置。

书中的示例代码:

public class SaveOffsetsOnRebalance implements ConsumerRebalanceListener {
    public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
        commitDBTransaction(); 
    }

    public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
        for(TopicPartition partition: partitions)
        consumer.seek(partition, getOffsetFromDB(partition)); 
    }
}

consumer.subscribe(topics, new SaveOffsetOnRebalance(consumer));
consumer.poll(0);

for (TopicPartition partition: consumer.assignment())
    consumer.seek(partition, getOffsetFromDB(partition));   

while (true) {
    ConsumerRecords<String, String> records = consumer.poll(100);
    for (ConsumerRecord<String, String> record : records)
    {
        processRecord(record);
        storeRecordInDB(record);
        storeOffsetInDB(record.topic(), record.partition(), record.offset()); 
    }
    commitDBTransaction();
}