com.azure.messaging.eventhubs.EventHubConsumerClient Maven / Gradle / Ivy
Show all versions of azure-messaging-eventhubs Show documentation
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.messaging.eventhubs;
import com.azure.core.annotation.ReturnType;
import com.azure.core.annotation.ServiceClient;
import com.azure.core.annotation.ServiceMethod;
import com.azure.core.util.Context;
import com.azure.core.util.IterableStream;
import com.azure.core.util.logging.ClientLogger;
import com.azure.messaging.eventhubs.implementation.SynchronousEventSubscriber;
import com.azure.messaging.eventhubs.implementation.SynchronousReceiveWork;
import com.azure.messaging.eventhubs.implementation.instrumentation.EventHubsTracer;
import com.azure.messaging.eventhubs.models.EventPosition;
import com.azure.messaging.eventhubs.models.PartitionEvent;
import com.azure.messaging.eventhubs.models.ReceiveOptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import java.io.Closeable;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import static com.azure.messaging.eventhubs.implementation.ClientConstants.PARTITION_ID_KEY;
/**
* A synchronous consumer responsible for reading {@link EventData} from an Event Hub partition in the context of
* a specific consumer group.
*
* Most receive operations contain a parameter {@code maxWaitTime}. The iterable is returned when either
* {@code maxWaitTime} has elapsed or {@code numberOfEvents} have been received. It is possible to have an empty
* iterable if no events were received in that time frame. {@link #receiveFromPartition(String, int, EventPosition)}
* does not have a parameter for {@code maxWaitTime}, consequently, it can take a long time to return results if
* {@code numberOfEvents} is too high and there is low traffic in that Event Hub.
*
* The examples shown in this document use a credential object named DefaultAzureCredential for authentication,
* which is appropriate for most scenarios, including local development and production environments. Additionally, we
* recommend using
* managed identity
* for authentication in production environments. You can find more information on different ways of authenticating and
* their corresponding credential types in the
* Azure Identity documentation".
*
*
* Sample: Creating a synchronous consumer
*
* The following code sample demonstrates the creation of the synchronous client {@link EventHubConsumerClient}.
* The {@code fullyQualifiedNamespace} is the Event Hubs Namespace's host name. It is listed under the "Essentials"
* panel after navigating to the Event Hubs Namespace via Azure Portal. The {@code consumerGroup} is found by
* navigating to the Event Hub instance, and selecting "Consumer groups" under the "Entities" panel. The
* {@link EventHubClientBuilder#consumerGroup(String)} is required for creating consumer clients.
*
*
*
* TokenCredential credential = new DefaultAzureCredentialBuilder().build();
*
* // "<<fully-qualified-namespace>>" will look similar to "{your-namespace}.servicebus.windows.net"
* // "<<event-hub-name>>" will be the name of the Event Hub instance you created inside the Event Hubs namespace.
* EventHubConsumerClient consumer = new EventHubClientBuilder()
* .credential("<<fully-qualified-namespace>>", "<<event-hub-name>>",
* credential)
* .consumerGroup(EventHubClientBuilder.DEFAULT_CONSUMER_GROUP_NAME)
* .buildConsumerClient();
*
*
*
* Sample: Consuming events from a single partition
*
* Events from a single partition can be consumed using {@link #receiveFromPartition(String, int, EventPosition)} or
* {@link #receiveFromPartition(String, int, EventPosition, Duration)}. The call to {@code receiveFromPartition}
* completes and returns an {@link IterableStream} when either the maximum number of events is received, or the
* timeout has elapsed. It is possible to have an empty iterable returned if there were no events received in that
* duration.
*
*
*
* TokenCredential credential = new DefaultAzureCredentialBuilder().build();
*
* // "<<fully-qualified-namespace>>" will look similar to "{your-namespace}.servicebus.windows.net"
* // "<<event-hub-name>>" will be the name of the Event Hub instance you created inside the Event Hubs namespace.
* EventHubConsumerClient consumer = new EventHubClientBuilder()
* .credential("<<fully-qualified-namespace>>", "<<event-hub-name>>",
* credential)
* .consumerGroup(EventHubClientBuilder.DEFAULT_CONSUMER_GROUP_NAME)
* .buildConsumerClient();
*
* Instant twelveHoursAgo = Instant.now().minus(Duration.ofHours(12));
* EventPosition startingPosition = EventPosition.fromEnqueuedTime(twelveHoursAgo);
* String partitionId = "0";
*
* // Reads events from partition '0' and returns the first 100 received or until the 30 seconds has elapsed.
* IterableStream<PartitionEvent> events = consumer.receiveFromPartition(partitionId, 100,
* startingPosition, Duration.ofSeconds(30));
*
* Long lastSequenceNumber = -1L;
* for (PartitionEvent partitionEvent : events) {
* // For each event, perform some sort of processing.
* System.out.print("Event received: " + partitionEvent.getData().getSequenceNumber());
* lastSequenceNumber = partitionEvent.getData().getSequenceNumber();
* }
*
* // Figure out what the next EventPosition to receive from is based on last event we processed in the stream.
* // If lastSequenceNumber is -1L, then we didn't see any events the first time we fetched events from the
* // partition.
* if (lastSequenceNumber != -1L) {
* EventPosition nextPosition = EventPosition.fromSequenceNumber(lastSequenceNumber, false);
*
* // Gets the next set of events from partition '0' to consume and process.
* IterableStream<PartitionEvent> nextEvents = consumer.receiveFromPartition(partitionId, 100,
* nextPosition, Duration.ofSeconds(30));
* }
*
*
*
* @see com.azure.messaging.eventhubs
* @see EventHubClientBuilder
*/
@ServiceClient(builder = EventHubClientBuilder.class)
public class EventHubConsumerClient implements Closeable {
private static final ClientLogger LOGGER = new ClientLogger(EventHubConsumerClient.class);
private final EventHubConsumerAsyncClient consumer;
private final ReceiveOptions defaultReceiveOptions = new ReceiveOptions();
private final Duration timeout;
private final AtomicInteger idGenerator = new AtomicInteger();
private final EventHubsTracer tracer;
EventHubConsumerClient(EventHubConsumerAsyncClient consumer, Duration tryTimeout) {
Objects.requireNonNull(tryTimeout, "'tryTimeout' cannot be null.");
this.consumer = Objects.requireNonNull(consumer, "'consumer' cannot be null.");
this.timeout = tryTimeout;
this.tracer = consumer.getInstrumentation().getTracer();
}
/**
* Gets the fully qualified Event Hubs namespace that the connection is associated with. This is likely similar to
* {@code {yournamespace}.servicebus.windows.net}.
*
* @return The fully qualified Event Hubs namespace that the connection is associated with.
*/
public String getFullyQualifiedNamespace() {
return consumer.getFullyQualifiedNamespace();
}
/**
* Gets the Event Hub name this client interacts with.
*
* @return The Event Hub name this client interacts with.
*/
public String getEventHubName() {
return consumer.getEventHubName();
}
/**
* Gets the consumer group this consumer is reading events as a part of.
*
* @return The consumer group this consumer is reading events as a part of.
*/
public String getConsumerGroup() {
return consumer.getConsumerGroup();
}
/**
* Retrieves information about an Event Hub, including the number of partitions present and their identifiers.
*
* @return The set of information for the Event Hub that this client is associated with.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public EventHubProperties getEventHubProperties() {
return consumer.getEventHubProperties().block();
}
/**
* Retrieves the identifiers for the partitions of an Event Hub.
*
* @return The set of identifiers for the partitions of an Event Hub.
*/
@ServiceMethod(returns = ReturnType.COLLECTION)
public IterableStream getPartitionIds() {
return new IterableStream<>(consumer.getPartitionIds());
}
/**
* Retrieves information about a specific partition for an Event Hub, including elements that describe the available
* events in the partition event stream.
*
* @param partitionId The unique identifier of a partition associated with the Event Hub.
*
* @return The set of information for the requested partition under the Event Hub this client is associated with.
*
* @throws NullPointerException if {@code partitionId} is null.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public PartitionProperties getPartitionProperties(String partitionId) {
return consumer.getPartitionProperties(partitionId).block();
}
/**
* Receives a batch of {@link PartitionEvent events} from the Event Hub partition.
*
* @param maximumMessageCount The maximum number of messages to receive in this batch.
* @param partitionId Identifier of the partition to read events from.
* @param startingPosition Position within the Event Hub partition to begin consuming events.
*
* @return A set of {@link PartitionEvent} that was received. The iterable contains up to
* {@code maximumMessageCount} events. If a stream for the events was opened before, the same position within
* that partition is returned. Otherwise, events are read starting from {@code startingPosition}.
*
* @throws NullPointerException if {@code partitionId}, or {@code startingPosition} is null.
* @throws IllegalArgumentException if {@code maximumMessageCount} is less than 1, or if {@code partitionId} is an
* empty string.
*/
@ServiceMethod(returns = ReturnType.COLLECTION)
public IterableStream receiveFromPartition(String partitionId, int maximumMessageCount,
EventPosition startingPosition) {
return receiveFromPartition(partitionId, maximumMessageCount, startingPosition, timeout);
}
/**
* Receives a batch of {@link PartitionEvent events} from the Event Hub partition.
*
* @param partitionId Identifier of the partition to read events from.
* @param maximumMessageCount The maximum number of messages to receive in this batch.
* @param startingPosition Position within the Event Hub partition to begin consuming events.
* @param maximumWaitTime The maximum amount of time to wait to build up the requested message count for the
* batch; if not specified, the default wait time specified when the consumer was created will be used.
*
* @return A set of {@link PartitionEvent} that was received. The iterable contains up to
* {@code maximumMessageCount} events.
*
* @throws NullPointerException if {@code partitionId}, {@code maximumWaitTime}, or {@code startingPosition} is
* {@code null}.
* @throws IllegalArgumentException if {@code maximumMessageCount} is less than 1 or {@code maximumWaitTime} is
* zero or a negative duration.
*/
@ServiceMethod(returns = ReturnType.COLLECTION)
public IterableStream receiveFromPartition(String partitionId, int maximumMessageCount,
EventPosition startingPosition, Duration maximumWaitTime) {
if (Objects.isNull(maximumWaitTime)) {
throw LOGGER.logExceptionAsError(new NullPointerException("'maximumWaitTime' cannot be null."));
} else if (Objects.isNull(startingPosition)) {
throw LOGGER.logExceptionAsError(new NullPointerException("'startingPosition' cannot be null."));
} else if (Objects.isNull(partitionId)) {
throw LOGGER.logExceptionAsError(new NullPointerException("'partitionId' cannot be null."));
}
if (partitionId.isEmpty()) {
throw LOGGER.logExceptionAsError(new IllegalArgumentException("'partitionId' cannot be empty."));
}
if (maximumMessageCount < 1) {
throw LOGGER.logExceptionAsError(
new IllegalArgumentException("'maximumMessageCount' cannot be less than 1."));
} else if (maximumWaitTime.isNegative() || maximumWaitTime.isZero()) {
throw LOGGER.logExceptionAsError(
new IllegalArgumentException("'maximumWaitTime' cannot be zero or less."));
}
Instant startTime = tracer.isEnabled() ? Instant.now() : null;
Flux events = Flux.create(emitter -> {
queueWork(partitionId, maximumMessageCount, startingPosition, maximumWaitTime, defaultReceiveOptions,
emitter);
});
events = tracer.reportSyncReceiveSpan("EventHubs.receiveFromPartition", startTime, events, Context.NONE);
return new IterableStream<>(events);
}
/**
* Receives a batch of {@link PartitionEvent events} from the Event Hub partition.
*
* @param partitionId Identifier of the partition to read events from.
* @param maximumMessageCount The maximum number of messages to receive in this batch.
* @param startingPosition Position within the Event Hub partition to begin consuming events.
* @param maximumWaitTime The maximum amount of time to wait to build up the requested message count for the
* batch; if not specified, the default wait time specified when the consumer was created will be used.
* @param receiveOptions Options when receiving events from the partition.
* @return A set of {@link PartitionEvent} that was received. The iterable contains up to
* {@code maximumMessageCount} events.
*
* @throws NullPointerException if {@code maximumWaitTime}, {@code startingPosition}, {@code partitionId}, or
* {@code receiveOptions} is {@code null}.
* @throws IllegalArgumentException if {@code maximumMessageCount} is less than 1 or {@code maximumWaitTime} is
* zero or a negative duration.
*/
@ServiceMethod(returns = ReturnType.COLLECTION)
public IterableStream receiveFromPartition(String partitionId, int maximumMessageCount,
EventPosition startingPosition, Duration maximumWaitTime, ReceiveOptions receiveOptions) {
if (Objects.isNull(maximumWaitTime)) {
throw LOGGER.logExceptionAsError(new NullPointerException("'maximumWaitTime' cannot be null."));
} else if (Objects.isNull(startingPosition)) {
throw LOGGER.logExceptionAsError(new NullPointerException("'startingPosition' cannot be null."));
} else if (Objects.isNull(partitionId)) {
throw LOGGER.logExceptionAsError(new NullPointerException("'partitionId' cannot be null."));
} else if (Objects.isNull(receiveOptions)) {
throw LOGGER.logExceptionAsError(new NullPointerException("'receiveOptions' cannot be null."));
}
if (partitionId.isEmpty()) {
throw LOGGER.logExceptionAsError(new IllegalArgumentException("'partitionId' cannot be empty."));
}
if (maximumMessageCount < 1) {
throw LOGGER.logExceptionAsError(
new IllegalArgumentException("'maximumMessageCount' cannot be less than 1."));
} else if (maximumWaitTime.isNegative() || maximumWaitTime.isZero()) {
throw LOGGER.logExceptionAsError(
new IllegalArgumentException("'maximumWaitTime' cannot be zero or less."));
}
Instant startTime = tracer.isEnabled() ? Instant.now() : null;
Flux events = Flux.create(emitter -> {
queueWork(partitionId, maximumMessageCount, startingPosition, maximumWaitTime, receiveOptions, emitter);
});
events = tracer.reportSyncReceiveSpan("EventHubs.receiveFromPartition", startTime, events, Context.NONE);
return new IterableStream<>(events);
}
/**
* {@inheritDoc}
*/
@Override
public void close() {
consumer.close();
}
/**
* Given an {@code emitter}, queues that work in {@link SynchronousEventSubscriber}. If the synchronous job has not
* been created, will initialise it.
*/
private void queueWork(String partitionId, int maximumMessageCount, EventPosition startingPosition,
Duration maximumWaitTime, ReceiveOptions receiveOptions, FluxSink emitter) {
final long id = idGenerator.getAndIncrement();
final SynchronousReceiveWork work = new SynchronousReceiveWork(id, maximumMessageCount, maximumWaitTime,
emitter);
final SynchronousEventSubscriber syncSubscriber = new SynchronousEventSubscriber(work);
LOGGER.atInfo()
.addKeyValue(PARTITION_ID_KEY, partitionId)
.log("Started synchronous event subscriber.");
consumer.receiveFromPartition(partitionId, startingPosition, receiveOptions).subscribeWith(syncSubscriber);
}
/**
* Gets the client identifier.
*
* @return The unique identifier string for current client.
*/
public String getIdentifier() {
return this.consumer.getIdentifier();
}
}