相关概念
位移提交:把消费位移存储起来(持久化)的动作称为位移提交
lastConsumedOffset:当前consumer已经消费到的消息
committedOffset:已经提交过的消费位移
position:下一次要拉取消息的位置
kafka默认自动提交,手动提交需要在配置文件中将其设为false
演示
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
public static void main(String[] args) {
TopicPartition topicPartition = new TopicPartition(topic, 0);
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(CommittedConsumer.initConfig());
consumer.assign(Collections.singletonList(topicPartition));
long lastConsumedOffset = -1;//当前消费到的位移
for (int i = 0; i < 50; i++) {
ConsumerRecords<String, String> poll = consumer.poll(Duration.ofMillis(1000));
if (poll.isEmpty()) {
continue;
}
//只订阅了一个tp,所以只要消费这一个
List<ConsumerRecord<String, String>> records = poll.records(topicPartition);
for (ConsumerRecord<String, String> record : records) {
//TODO
}
lastConsumedOffset = records.get(records.size() - 1).offset();
consumer.commitSync(); //无参的同步位移提交
}
long position = consumer.position(topicPartition);
OffsetAndMetadata committed = consumer.committed(topicPartition);
long committedOffset = committed.offset();
System.out.println("lastConsumedOffset: " + lastConsumedOffset + " committedOffset: " + committedOffset
+ " position: " + position);
}
从结果上看,因为发送的消息都消费成功了, 所以position = committedOffset = lastConsumedOffset + 1。如果消费过程中出现了错误,就有可能会造成重复消费或者消息丢失,具体是哪一种取决于位移提交的时机,以及消费的方式
重复消费与消息丢失
重复消费
当消费x+5时发生了异常,且还没进行消费提交,故障恢复后,重新拉去的消息是从x+2开始,这导致x+2到x+之间的消息又重新消费了一遍。
见上面的演示代码
消息丢失
这种也很好理解,举个例子,另起一个线程,用来对拉取的消息进行消费,而主线程在将消息交付后已经位移提交了。一旦之前拉取的消息并没有消费成功,就发生了消息丢失。另起线程进行消费的代码可以在consuner多线程篇看到
异步提交、更细粒度提交
无参的commitSync()只能一次性将所有拉取的消息都提交,如果只想提交中间的Offset,可以使用有参的方法,具体的值按照业务处理来。除此之外,Kafka还提供了异步提交的方法commitAsync,当提交后会执行其中的onComplete方法,这一点和producer的send()是类似的
consumer.commitAsync(Collections.singletonMap(topicPartition, new OffsetAndMetadata(lastConsumedOffset)),
new OffsetCommitCallback() {
@Override
public void onComplete(Map<TopicPartition, OffsetAndMetadata> map, Exception e) {
if (e == null) {
System.out.println("消费成功! offset:" + JSON.toJSONString(map));
} else {
//TODO
}
}
});