/**
* Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain a
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sixt.service.framework.kafka;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition;
import java.time.Clock;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Offsets for a partition of a topic for a given consumer group are only kept
* for a limited time. To ensure that we don't lose the offsets in inactive
* partitions, we periodically recommit the offsets.
*/
public class OffsetCommitter {
final static Duration IDLE_DURATION = Duration.ofHours(1);
private final KafkaConsumer<String, String> consumer;
private final Clock clock;
protected final Map<TopicPartition, OffsetAndTime> offsetData;
protected LocalDateTime lastUpdateTime;
public OffsetCommitter(KafkaConsumer<String, String> consumer, Clock clock) {
this.consumer = consumer;
this.clock = clock;
offsetData = new HashMap<>();
lastUpdateTime = LocalDateTime.now(clock);
}
public void partitionsAssigned(Collection<TopicPartition> partitions) {
if (partitions != null) {
LocalDateTime now = LocalDateTime.now(clock);
for (TopicPartition tp : partitions) {
long offset = consumer.position(tp);
offsetData.put(tp, new OffsetAndTime(offset, now));
}
}
}
public void partitionsRevoked(Collection<TopicPartition> partitions) {
if (partitions != null) {
for (TopicPartition tp : partitions) {
offsetData.remove(tp);
}
}
}
public void offsetCommitted(Map<TopicPartition, OffsetAndMetadata> offsetMap) {
if (offsetMap != null) {
LocalDateTime now = LocalDateTime.now(clock);
for (TopicPartition tp : offsetMap.keySet()) {
OffsetAndMetadata offsetAndMetadata = offsetMap.get(tp);
offsetData.put(tp, new OffsetAndTime(offsetAndMetadata.offset(), now));
}
}
}
public void recommitOffsets() {
LocalDateTime now = LocalDateTime.now(clock);
if (now.isAfter(lastUpdateTime.plus(IDLE_DURATION))) {
for (TopicPartition tp : offsetData.keySet()) {
OffsetAndTime offsetAndTime = offsetData.get(tp);
if (now.isAfter(offsetAndTime.time.plus(IDLE_DURATION))) {
consumer.commitSync(Collections.singletonMap(tp,
new OffsetAndMetadata(offsetAndTime.offset)));
offsetAndTime.time = now;
}
}
lastUpdateTime = now;
}
}
private class OffsetAndTime {
long offset;
LocalDateTime time;
OffsetAndTime(long offset, LocalDateTime time) {
this.offset = offset;
this.time = time;
}
}
}