我正在尝试读取请求的kafka消息数量。 对于非事务性消息,我们将从endoffset中寻求-M个分区的N个开始轮询并收集当前偏移量小于每个分区的结束偏移量的消息。对于幂等/事务性消息,我们必须考虑事务标记/重复消息,这意味着偏移不会是连续的,在这种情况下,endoffset-N将不会返回N条消息,我们需要返回并寻找更多消息,直到我们收到N条消息每个分区或达到起始偏移量
由于存在多个分区,因此我需要跟踪读取的所有偏移量,以便在完成所有操作后停止。有两个步骤,第一步是计算开始偏移量(结束偏移量-请求的消息数量)和结束偏移量。 (偏移量是不连续的,存在间隙),我将寻求从起始偏移量开始消耗的分区。第二步是轮询消息并计数每个分区中的消息,如果不满足请求的消息数,请再次重复第一步和第二步,直到遇到每个分区的消息数为止。
条件
初始轮询可能不会返回任何记录,因此请继续轮询。 当达到每个分区的结束偏移量时停止轮询,否则轮询不返回任何结果。 检查每个分区,以读取与请求的消息相同的消息。如果是,则标记为完成,如果没有,则标记为继续并重复步骤。解决消息中的空白。 应该适用于交易和非交易生产者。
问题:
我将如何跟踪每个分区已读取的所有消息并退出循环?如果有帮助,每个分区中的消息将按顺序排列。
spring kafka是否支持这种用例?可以找到更多详细信息here
更新:我要读取每个分区中的最后N条消息。分区而不是消息是用户输入。我想将所有偏移量管理保留在内存中。本质上,我们试图按LIFO顺序读取消息。由于Kafka允许您向前阅读而不向后阅读,因此这很棘手。
答案 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;
}