-
Notifications
You must be signed in to change notification settings - Fork 182
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for Kafka's ConsumerRebalanceListener
- Loading branch information
Showing
13 changed files
with
460 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
documentation/src/main/doc/modules/kafka/examples/inbound/KafkaRebalancedConsumer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package inbound; | ||
|
||
import io.smallrye.reactive.messaging.kafka.IncomingKafkaRecord; | ||
import org.eclipse.microprofile.reactive.messaging.Incoming; | ||
|
||
import javax.enterprise.context.ApplicationScoped; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.CompletionStage; | ||
|
||
@ApplicationScoped | ||
public class KafkaRebalancedConsumer { | ||
|
||
@Incoming("rebalanced-example") | ||
public CompletionStage<Void> consume(IncomingKafkaRecord<Integer, String> message) { | ||
// we don't need to commit offsets so no need to ack the message | ||
return CompletableFuture.completedFuture(null); | ||
} | ||
|
||
} |
71 changes: 71 additions & 0 deletions
71
...src/main/doc/modules/kafka/examples/inbound/KafkaRebalancedConsumerRebalanceListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package inbound; | ||
|
||
import io.smallrye.mutiny.Uni; | ||
import io.smallrye.reactive.messaging.kafka.KafkaConsumerRebalanceListener; | ||
import io.vertx.kafka.client.common.TopicPartition; | ||
import io.vertx.mutiny.kafka.client.consumer.KafkaConsumer; | ||
|
||
import javax.enterprise.context.ApplicationScoped; | ||
import javax.inject.Named; | ||
import java.util.Set; | ||
import java.util.logging.Logger; | ||
import java.util.stream.Collectors; | ||
|
||
@ApplicationScoped | ||
@Named("rebalanced-example.rebalancer") | ||
public class KafkaRebalancedConsumerRebalanceListener implements KafkaConsumerRebalanceListener { | ||
|
||
private static final Logger LOGGER = Logger.getLogger(KafkaRebalancedConsumerRebalanceListener.class.getName()); | ||
|
||
/** | ||
* When receiving a list of partitions will search for the earliest offset within 10 minutes | ||
* and seek the consumer to it. These operations are asynchronous so the inbound connector's | ||
* consumer WILL be paused until they are compete. | ||
* | ||
* @param consumer underlying consumer | ||
* @param topicPartitions set of assigned topic partitions | ||
* @return An observable | ||
*/ | ||
@Override | ||
public Uni<Void> onPartitionsAssigned(KafkaConsumer<?, ?> consumer, Set<TopicPartition> topicPartitions) { | ||
// we must pause the consumer otherwise the inbound connector will continue to receive messages | ||
// while we seek to the correct offset. | ||
consumer.pause(); | ||
|
||
long now = System.currentTimeMillis(); | ||
long shouldStartAt = now - 600_000L; //10 minute ago | ||
|
||
return Uni | ||
.combine() | ||
.all() | ||
.unis(topicPartitions | ||
.stream() | ||
.map(topicPartition -> { | ||
LOGGER.info("Assigned " + topicPartition); | ||
return consumer.offsetsForTimes(topicPartition, shouldStartAt) | ||
.onItem() | ||
.invoke(o -> LOGGER.info("Seeking to " + o)) | ||
.onItem() | ||
.produceUni(o -> consumer | ||
.seek(topicPartition, o == null ? 0L : o.getOffset()) | ||
.onItem() | ||
.invoke(v -> LOGGER.info("Seeked to " + o)) | ||
); | ||
}) | ||
.collect(Collectors.toList())) | ||
.combinedWith(a -> null) | ||
.onItemOrFailure() | ||
.apply((a, t) -> { | ||
// once the seek is complete let's resume the consumer | ||
consumer.resume(); | ||
return null; | ||
}); | ||
} | ||
|
||
@Override | ||
public Uni<Void> onPartitionsRevoked(KafkaConsumer<?, ?> consumer, Set<TopicPartition> topicPartitions) { | ||
return Uni | ||
.createFrom() | ||
.nullItem(); | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
documentation/src/main/doc/modules/kafka/pages/consumer-rebalance-listener.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
[#kafka-consumer-rebalance-listener] | ||
=== Consumer Rebalance Listener | ||
|
||
An implementation of the consumer rebalance listener can be provided to provide fine grain controls of the assigned | ||
offset. Common uses are storing offsets in a separate store to enable deliver exactly-once semantics, and starting from | ||
a specific time window. | ||
|
||
==== Example | ||
|
||
In this example we will set-up a consumer that always starts on messages from at most 10 minutes ago. First we need to provide | ||
a bean managed implementation of `io.smallrye.reactive.messaging.kafka.KafkaConsumerRebalanceListener` annotated with | ||
`javax.inject.Named`. We then must configure our inbound connector to use this named bean. | ||
|
||
[source, java] | ||
---- | ||
include::example$inbound/KafkaRebalancedConsumerRebalanceListener.java[] | ||
---- | ||
|
||
[source, java] | ||
---- | ||
include::example$inbound/KafkaRebalancedConsumer.java[] | ||
---- | ||
|
||
Configure the inbound connector to use the provided listener: | ||
|
||
* `mp.messaging.incoming.rebalanced-example.consumer-rebalance-listener.name=rebalanced-example.rebalancer` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
...ka/src/main/java/io/smallrye/reactive/messaging/kafka/KafkaConsumerRebalanceListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package io.smallrye.reactive.messaging.kafka; | ||
|
||
import java.util.Set; | ||
|
||
import io.smallrye.mutiny.Uni; | ||
import io.vertx.kafka.client.common.TopicPartition; | ||
import io.vertx.mutiny.kafka.client.consumer.KafkaConsumer; | ||
|
||
/** | ||
* | ||
* When implemented by a managed bean annotated with {@link javax.inject.Named} and | ||
* configured against a consumer, ex: | ||
* mp.messaging.incoming.example.consumer-rebalance-listener.name=ExampleConsumerRebalanceListener | ||
* | ||
* Will be applied as a consumer rebalance listener to the consumer. | ||
* | ||
* For more details | ||
* | ||
* @see org.apache.kafka.clients.consumer.ConsumerRebalanceListener | ||
*/ | ||
public interface KafkaConsumerRebalanceListener { | ||
|
||
/** | ||
* Called when the consumer is assigned topic partitions | ||
* | ||
* @param consumer underlying consumer | ||
* @param topicPartitions set of assigned topic partitions | ||
* @return An observable | ||
*/ | ||
Uni<Void> onPartitionsAssigned(KafkaConsumer<?, ?> consumer, Set<TopicPartition> topicPartitions); | ||
|
||
/** | ||
* Called when the consumer is revoked topic partitions | ||
* | ||
* @param consumer underlying consumer | ||
* @param topicPartitions set of revoked topic partitions | ||
* @return An observable | ||
*/ | ||
Uni<Void> onPartitionsRevoked(KafkaConsumer<?, ?> consumer, Set<TopicPartition> topicPartitions); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
...g-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/ConsumptionBeanWithoutAck.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package io.smallrye.reactive.messaging.kafka; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
import javax.enterprise.context.ApplicationScoped; | ||
|
||
import org.eclipse.microprofile.reactive.messaging.Acknowledgment; | ||
import org.eclipse.microprofile.reactive.messaging.Incoming; | ||
import org.eclipse.microprofile.reactive.messaging.Message; | ||
import org.eclipse.microprofile.reactive.messaging.Outgoing; | ||
|
||
@ApplicationScoped | ||
public class ConsumptionBeanWithoutAck { | ||
|
||
private final List<Integer> list = Collections.synchronizedList(new ArrayList<>()); | ||
private final List<KafkaRecord<String, Integer>> kafka = Collections.synchronizedList(new ArrayList<>()); | ||
|
||
@Incoming("data") | ||
@Outgoing("sink") | ||
@Acknowledgment(Acknowledgment.Strategy.MANUAL) | ||
public Message<Integer> process(KafkaRecord<String, Integer> input) { | ||
kafka.add(input); | ||
return Message.of(input.getPayload() + 1); | ||
} | ||
|
||
@Incoming("sink") | ||
public void sink(int val) { | ||
list.add(val); | ||
} | ||
|
||
public List<Integer> getResults() { | ||
return list; | ||
} | ||
|
||
public List<KafkaRecord<String, Integer>> getKafkaMessages() { | ||
return kafka; | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
.../test/java/io/smallrye/reactive/messaging/kafka/ConsumptionConsumerRebalanceListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package io.smallrye.reactive.messaging.kafka; | ||
|
||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
import javax.enterprise.context.ApplicationScoped; | ||
import javax.inject.Named; | ||
|
||
import io.smallrye.mutiny.Uni; | ||
import io.vertx.kafka.client.common.TopicPartition; | ||
import io.vertx.mutiny.kafka.client.consumer.KafkaConsumer; | ||
|
||
@ApplicationScoped | ||
@Named("ConsumptionConsumerRebalanceListener") | ||
public class ConsumptionConsumerRebalanceListener implements KafkaConsumerRebalanceListener { | ||
|
||
private final Map<Integer, TopicPartition> assigned = new ConcurrentHashMap<>(); | ||
|
||
@Override | ||
public Uni<Void> onPartitionsAssigned(KafkaConsumer<?, ?> consumer, Set<TopicPartition> set) { | ||
set.forEach(topicPartition -> this.assigned.put(topicPartition.getPartition(), topicPartition)); | ||
return Uni | ||
.createFrom() | ||
.nullItem(); | ||
} | ||
|
||
@Override | ||
public Uni<Void> onPartitionsRevoked(KafkaConsumer<?, ?> consumer, Set<TopicPartition> set) { | ||
return Uni | ||
.createFrom() | ||
.nullItem(); | ||
} | ||
|
||
public Map<Integer, TopicPartition> getAssigned() { | ||
return assigned; | ||
} | ||
} |
Oops, something went wrong.