
com.google.cloud.pubsub.Subscriber Maven / Gradle / Ivy
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* 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.google.cloud.pubsub;
import com.google.auth.Credentials;
import com.google.cloud.pubsub.Subscriber.MessageReceiver.AckReply;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.Service;
import com.google.pubsub.v1.PubsubMessage;
import io.grpc.Channel;
import java.util.concurrent.ScheduledExecutorService;
import org.joda.time.Duration;
/**
* A Cloud Pub/Sub subscriber that is
* associated with a specific subscription at creation.
*
* A {@link Subscriber} allows you to provide an implementation of a {@link MessageReceiver
* receiver} to which messages are going to be delivered as soon as they are received by the
* subscriber. The delivered messages then can be {@link AcknowledgeHandler#ack() acked} or {@link
* AcknowledgeHandler#nack() nacked} at will as they get processed by the receiver. Nacking a
* messages implies a later redelivery of such message.
*
*
The subscriber handles the ack management, by automatically extending the ack deadline while
* the message is being processed, to then issue the ack or nack of such message when the processing
* is done. Note: message redelivery is still possible.
*
*
It also provides customizable options that control:
*
*
* - Ack deadline extension: such as the amount of time ahead to trigger the extension of
* message acknowledgement expiration.
*
- Flow control: such as the maximum outstanding messages or maximum outstanding bytes to keep
* in memory before the receiver either ack or nack them.
*
*
* If no credentials are provided, the {@link Publisher} will use application default
* credentials through {@link GoogleCredentials#getApplicationDefault}.
*
*
For example, a {@link Subscriber} can be constructed and used to receive messages as follows:
*
*
* MessageReceiver receiver =
* message -> {
* // ... process message ...
* return Futures.immediateFuture(AckReply.ACK);
* });
*
* Subscriber subscriber =
* Subscriber.Builder.newBuilder(MY_SUBSCRIPTION, receiver)
* .setMaxBatchAcks(100)
* .build();
*
* subscriber.startAsync();
*
* ... recommended, listen for fatal errors that break the subscriber streaming ...
* subscriber.addListener(
new Listener() {
@Override
public void failed(State from, Throwable failure) {
System.out.println("Subscriber faile with error: " + failure);
}
},
Executors.newSingleThreadExecutor());
*
* ... and when done with the subscriber ...
* subscriber.stopAsync();
*
*/
public interface Subscriber extends Service {
static final String PUBSUB_API_ADDRESS = "pubsub.googleapis.com";
static final String PUBSUB_API_SCOPE = "https://www.googleapis.com/auth/pubsub";
/** Retrieves a snapshot of the current subscriber statistics. */
SubscriberStats getStats();
/** Users of the {@link Subscriber} must implement this interface to receive messages. */
interface MessageReceiver {
public static enum AckReply {
/**
* To be used for acking a message.
*/
ACK,
/**
* To be used for nacking a message.
*/
NACK
}
/**
* Called when a message is received by the subscriber.
*
* @return A future that signals when a message has been processed.
*/
ListenableFuture receiveMessage(PubsubMessage message);
}
/** Subscription for which the subscriber is streaming messages. */
String getSubscription();
/**
* Time before a message is to expire when the subscriber is going to attempt to renew its ack
* deadline.
*/
Duration getAckExpirationPadding();
/**
* Maximum number of outstanding (i.e. pending to process) messages before limits are enforced.
*
* When limits are enforced, no more messages will be dispatched to the {@link
* MessageReceiver} but due to the gRPC and HTTP/2 buffering and congestion control window
* management, still some extra bytes could be kept at lower layers.
*/
Optional getMaxOutstandingMessages();
/**
* Maximum number of outstanding (i.e. pending to process) bytes before limits are enforced.
*/
Optional getMaxOutstandingBytes();
/** Builder of {@link Subscriber Subscribers}. */
public static final class Builder {
static final Duration MIN_ACK_EXPIRATION_PADDING = Duration.millis(100);
static final Duration DEFAULT_ACK_EXPIRATION_PADDING = Duration.millis(500);
String subscription;
Optional credentials;
MessageReceiver receiver;
Duration ackExpirationPadding;
Optional maxOutstandingMessages;
Optional maxOutstandingBytes;
Optional executor;
Optional channel;
/**
* Constructs a new {@link Builder}.
*
* Once {@link #build()} is called a gRPC stub will be created for use of the {@link
* Publisher}.
*
* @param subscription Cloud Pub/Sub subscription to bind the subscriber to
* @param receiver an implementation of {@link MessageReceiver} used to process the received
* messages
*/
public static Builder newBuilder(String subscription, MessageReceiver receiver) {
return new Builder(subscription, receiver);
}
Builder(String subscription, MessageReceiver receiver) {
setDefaults();
this.subscription = subscription;
this.receiver = receiver;
}
private void setDefaults() {
credentials = Optional.absent();
channel = Optional.absent();
ackExpirationPadding = DEFAULT_ACK_EXPIRATION_PADDING;
maxOutstandingBytes = Optional.absent();
maxOutstandingMessages = Optional.absent();
executor = Optional.absent();
}
/**
* Credentials to authenticate with.
*
*
Must be properly scoped for accessing Cloud Pub/Sub APIs.
*/
public Builder setCredentials(Credentials credentials) {
this.credentials = Optional.of(Preconditions.checkNotNull(credentials));
return this;
}
/**
* Channel to use.
*
*
Must point at Cloud Pub/Sub endpoint.
*/
public Builder setChannel(Channel channel) {
this.channel = Optional.of(Preconditions.checkNotNull(channel));
return this;
}
/**
* Sets the maximum number of outstanding messages; messages delivered to the {@link
* MessageReceiver} that have not been acknowledged or rejected.
*
* @param maxOutstandingMessages must be greater than 0
*/
public void setMaxOutstandingMessages(int maxOutstandingMessages) {
Preconditions.checkArgument(
maxOutstandingMessages > 0,
"maxOutstandingMessages limit is disabled by default, but if set it must be set to a "
+ "value greater to 0.");
this.maxOutstandingMessages = Optional.of(maxOutstandingMessages);
}
/**
* Sets the maximum number of outstanding bytes; bytes delivered to the {@link MessageReceiver}
* that have not been acknowledged or rejected.
*
* @param maxOutstandingBytes must be greater than 0
*/
public void setMaxOutstandingBytes(int maxOutstandingBytes) {
Preconditions.checkArgument(
maxOutstandingBytes > 0,
"maxOutstandingBytes limit is disabled by default, but if set it must be set to a value "
+ "greater than 0.");
this.maxOutstandingBytes = Optional.of(maxOutstandingBytes);
}
/**
* Set acknowledgement expiration padding.
*
*
This is the time accounted before a message expiration is to happen, so the
* {@link Subscriber} is able to send an ack extension beforehand.
*
*
This padding duration is configurable so you can account for network latency. A reasonable
* number must be provided so messages don't expire because of network latency between when the
* ack extension is required and when it reaches the Pub/Sub service.
*
* @param ackExpirationPadding must be greater or equal to {@link #MIN_ACK_EXPIRATION_PADDING}
*/
public void setAckExpirationPadding(Duration ackExpirationPadding) {
Preconditions.checkArgument(ackExpirationPadding.compareTo(MIN_ACK_EXPIRATION_PADDING) >= 0);
this.ackExpirationPadding = ackExpirationPadding;
}
/** Gives the ability to set a custom executor. */
public void setExecutor(ScheduledExecutorService executor) {
this.executor = Optional.of(executor);
}
public Subscriber build() {
return new SubscriberImpl(this);
}
}
}