Spring Kafka-消费任何主题的分区的最后N条消息

时间:2019-10-11 10:48:03

标签: java spring apache-kafka apache-kafka-streams spring-kafka

我正在尝试读取请求的kafka消息数量。 对于非事务性消息,我们将从endoffset中寻求-M个分区的N个开始轮询并收集当前偏移量小于每个分区的结束偏移量的消息。对于幂等/事务性消息,我们必须考虑事务标记/重复消息,这意味着偏移不会是连续的,在这种情况下,endoffset-N将不会返回N条消息,我们需要返回并寻找更多消息,直到我们收到N条消息每个分区或达到起始偏移量

由于存在多个分区,因此我需要跟踪读取的所有偏移量,以便在完成所有操作后停止。有两个步骤,第一步是计算开始偏移量(结束偏移量-请求的消息数量)和结束偏移量。 (偏移量是不连续的,存在间隙),我将寻求从起始偏移量开始消耗的分区。第二步是轮询消息并计数每个分区中的消息,如果不满足请求的消息数,请再次重复第一步和第二步,直到遇到每个分区的消息数为止。

条件

初始轮询可能不会返回任何记录,因此请继续轮询。 当达到每个分区的结束偏移量时停止轮询,否则轮询不返回任何结果。 检查每个分区,以读取与请求的消息相同的消息。如果是,则标记为完成,如果没有,则标记为继续并重复步骤。解决消息中的空白。 应该适用于交易和非交易生产者。

问题:

我将如何跟踪每个分区已读取的所有消息并退出循环?如果有帮助,每个分区中的消息将按顺序排列。

spring kafka是否支持这种用例?可以找到更多详细信息here

更新:我要读取每个分区中的最后N条消息。分区而不是消息是用户输入。我想将所有偏移量管理保留在内存中。本质上,我们试图按LIFO顺序读取消息。由于Kafka允许您向前阅读而不向后阅读,因此这很棘手。

2 个答案:

答案 0 :(得分:0)

为什么我有这种需要,我不明白。当队列中没有任何内容时,Kafka自己进行管理。如果消息从一个状态跳到另一个状态,则可以有单独的队列/主题。但是,这是一种方法。

当我们使用类似-

的方式来使用来自分区的消息时
ConsumerIterator<byte[], byte[]> it = something; //initialize consumer
while (it.hasNext()) {
  MessageAndMetadata<byte[], byte[]> messageAndMetadata = it.next();
  String kafkaMessage = new String(messageAndMetadata.message());
  int partition = messageAndMetadata.partition();
  long offset = messageAndMetadata.offset();
  boolean processed = false;
  do{
    long maxOffset = something; //fetch from db
    //if offset<maxOffset, then process messages and manual commit
    //else busy wait or something more useful
  }while(processed);
}

我们获得有关偏移量,分区号和消息本身的信息。您可以选择对此信息进行任何操作。

对于您的用例,您可能还决定将消耗的偏移量保存到数据库中,以便下次可以调整偏移量。另外,我建议关闭连接进行清理并最终将处理后的偏移量保存到数据库。

答案 1 :(得分:0)

因此,如果我对您的理解正确,那么应该可以使用标准的Kafka Consumer

Consumer<?, Message> consumer = ...

public Map<Integer, List<Message>> readLatestFromPartitions(String topic, Collection<Integer> partitions, int count) {

    // create the TopicPartitions we want to read
    List<TopicPartition> tps = partitions.stream().map(p -> new TopicPartition(topic, p)).collect(toList());
    consumer.assign(tps);

    // create and initialize the result map
    Map<Integer, List<Message>> result = new HashMap<>();
    for (Integer i : partitions) { result.add(new ArrayList<>()); }

    // read until the expected count has been read for all partitions
    while (result.valueSet().stream().findAny(l -> l.size() < count)) {
        // read until the end of the topic
        ConsumerRecords<?, Message> records = consumer.poll(Duration.ofSeconds(5));
        while (records.count() > 0) {
            Iterator<ConsumerRecord<?, Message>> recordIterator = records.iterator();
            while (recordIterator.hasNext()) {
                ConsumerRecord<?, Message> record = recordIterator.next();
                List<Message> addTo = result.get(record.partition);
                // only allow 10 entries per partition
                if (addTo.size() >= count) {
                    addTo.remove(0);
                }
                addTo.add(record.value);
            }
            records = consumer.poll(Duration.ofSeconds(5));
        }
        // now we have read the whole topic for the given partitions.
        // if all lists contain the expected count, the loop will finish;
        // otherwise it will wait for more data to arrive.
    }

    // the map now contains the messages in the order they were sent,
    // we want them reversed (LIFO)
    Map<Integer, List<Message>> returnValue = new HashMap<>();
    result.forEach((k, v) -> returnValue.put(k, Collections.reverse(v)));
    return returnValue;
}