consumer位移提交

155 阅读2分钟

相关概念

位移提交:把消费位移存储起来(持久化)的动作称为位移提交
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
                            }
                        }
                    });