Entrega exactamente una vez

En esta página se explica cómo recibir y confirmar mensajes mediante la función de procesamiento una vez solo de Pub/Sub, que te permite hacer un seguimiento de los mensajes y evitar que se procesen dos veces. Cuando la función está habilitada, Pub/Sub proporciona la siguiente semántica:

  • Los suscriptores pueden determinar si los acuses de recibo de los mensajes se han enviado correctamente.

  • No se vuelve a enviar el mensaje después de que se haya confirmado correctamente.

  • No se vuelve a enviar un mensaje mientras esté pendiente. Un mensaje se considera pendiente hasta que vence el plazo de confirmación o hasta que se confirma.

  • En caso de que haya varias entregas válidas, debido a que ha vencido el plazo de confirmación o a que el cliente ha iniciado una confirmación negativa, solo se puede usar el ID de confirmación más reciente para confirmar el mensaje. Las solicitudes con un ID de confirmación anterior fallarán.

Si se habilita el procesamiento exacto, los suscriptores pueden asegurarse de que los mensajes se procesen una vez siguiendo estas directrices:

  • Confirma los mensajes antes de la fecha límite.

  • Mantener información sobre el progreso del procesamiento de un mensaje hasta que se confirme correctamente.

  • Usa la información sobre el progreso del procesamiento de un mensaje para evitar que se duplique el trabajo cuando falla una confirmación.

Solo el tipo de suscripción de extracción admite la entrega exactamente una vez, incluidos los suscriptores que usan la API StreamingPull. Las suscripciones push y de exportación no admiten la entrega exactamente una vez.

Pub/Sub admite la entrega exactamente una vez en una región de la nube, basada en un ID de mensaje único definido por Pub/Sub.

Volver a enviar frente a duplicar

Es importante entender la diferencia entre las retransmisiones esperadas e inesperadas.

  • Una nueva entrega puede producirse debido a una confirmación negativa iniciada por el cliente de un mensaje o cuando el cliente no amplía el plazo de confirmación del mensaje antes de que expire. Las reentregas se consideran válidas y el sistema funciona según lo previsto.

    Para solucionar problemas de reenvío, consulta Gestionar duplicados.

  • Se produce una duplicación cuando se vuelve a enviar un mensaje después de que se haya confirmado correctamente o antes de que caduque el plazo de confirmación.

  • Un mensaje reenviado conserva el mismo ID de mensaje entre los intentos de reenvío.

Las suscripciones con la entrega exactamente una vez habilitada no reciben entregas duplicadas.

Compatibilidad con la entrega exactamente una vez en bibliotecas de cliente

  • Las bibliotecas de cliente admitidas tienen una interfaz para la confirmación con respuesta (por ejemplo, Go). Puedes usar esta interfaz para comprobar si la solicitud de confirmación se ha realizado correctamente. Si la solicitud de confirmación se realiza correctamente, los clientes tienen la garantía de no recibir una nueva entrega. Si la solicitud de confirmación falla, los clientes pueden esperar que se vuelva a enviar.

  • Los clientes también pueden usar las bibliotecas de cliente admitidas sin la interfaz de confirmación. Sin embargo, en estos casos, los errores de confirmación pueden provocar que los mensajes se vuelvan a enviar de forma silenciosa.

  • Las bibliotecas de cliente admitidas tienen interfaces para definir el tiempo mínimo de extensión de la concesión (por ejemplo, Go). Debe asignar un valor alto a la extensión mínima de la concesión para evitar que caduquen las confirmaciones relacionadas con la red. El valor máximo es de 600 segundos.

  • Si usas la biblioteca de cliente Java e inicializas tu suscriptor con un canal gRPC personalizado mediante el método setChannelProvider(), te recomendamos que también definas maxInboundMetadataSize en al menos 1 MB al crear tu TransportChannelProvider. Para esta configuración, puedes usar el método InstantiatingGrpcChannelProvider.Builder.setMaxInboundMetadataSize() o el método ManagedChannelBuilder.maxInboundMetadataSize().

Los valores predeterminados y el intervalo de las variables relacionadas con la entrega exactamente una vez y los nombres de las variables pueden variar en las distintas bibliotecas de cliente. Por ejemplo, en la biblioteca de cliente de Java, las siguientes variables controlan el envío exactamente una vez.

Variable Descripción Valor
setEnableExactlyOnceDelivery Habilita o inhabilita el envío exactamente una vez. verdadero o falso. Valor predeterminado: falso.
minDurationPerAckExtension Tiempo mínimo en segundos que se debe usar para ampliar el plazo de confirmación de la modificación. Intervalo: de 0 a 600. Valor predeterminado: ninguno.
maxDurationPerAckExtension Tiempo máximo en segundos que se puede usar para ampliar el plazo de confirmación de la modificación. Intervalo: de 0 a 600. Valor predeterminado: ninguno.

En el caso de la entrega exactamente una vez, la solicitud modifyAckDeadline o acknowledgment a Pub/Sub falla cuando el ID de confirmación ya ha caducado. En estos casos, el servicio considera que el ID de confirmación caducado no es válido, ya que es posible que ya se haya enviado una entrega más reciente. Este es el comportamiento programado para la entrega exactamente una vez. A continuación, verás que las solicitudes acknowledgment y ModifyAckDeadline devuelven una respuesta INVALID_ARGUMENT. Cuando la entrega exactamente una vez está inhabilitada, estas solicitudes devuelven OK en los casos en los que los IDs de confirmación han caducado.

Para asegurarse de que las solicitudes acknowledgment y ModifyAckDeadline tengan IDs de confirmación válidos, puede asignar un valor alto a minDurationPerAckExtension.

Consideraciones regionales

La garantía de entrega exactamente una vez solo se aplica cuando los suscriptores se conectan al servicio en la misma región. Si tu aplicación de suscriptor se distribuye en varias regiones, puede provocar que los mensajes se entreguen por duplicado, incluso cuando la entrega exactamente una vez esté habilitada. Los editores pueden enviar mensajes a cualquier región y se sigue manteniendo la garantía de entrega exactamente una vez.

Cuando ejecutas tu aplicación en Google Cloud, se conecta de forma predeterminada al endpoint de Pub/Sub de la misma región. Por lo tanto, si ejecutas tu aplicación en una sola región de Google Cloud, por lo general, te aseguras de interactuar con una sola región.

Si ejecutas tu aplicación de suscriptor fuera de Google Cloud o en varias regiones, puedes asegurarte de que te conectas a una sola región usando un endpoint de ubicación al configurar tu cliente de Pub/Sub. Todos los endpoints de ubicación de Pub/Sub apuntan a regiones únicas. Para obtener más información sobre los endpoints de ubicación, consulta Endpoints de Pub/Sub. Para ver una lista de todos los endpoints de ubicación de Pub/Sub, consulta la lista de endpoints de ubicación.

Crear suscripciones con entrega exactamente una vez

Puedes crear una suscripción con entrega exactamente una vez mediante la consola de Google Cloud, la CLI de Google Cloud, la biblioteca de cliente o la API de Pub/Sub. Google Cloud

Suscripción de extracción

Consola

Para crear una suscripción de extracción con entrega exactamente una vez, sigue estos pasos:

  1. En la Google Cloud consola, ve a la página Suscripciones.

    Ir a Suscripciones

  2. Haz clic en Crear suscripción.

  3. Introduce el ID de suscripción.

  4. Elige o crea un tema en el menú desplegable.

    La suscripción recibe mensajes del tema.

  5. En la sección Entrega exactamente una vez, seleccione Habilitar entrega exactamente una vez.

  6. Haz clic en Crear.

gcloud

Para crear una suscripción de extracción con entrega exactamente una vez, usa el comando gcloud pubsub subscriptions create con la marca --enable-exactly-once-delivery:

gcloud pubsub subscriptions create SUBSCRIPTION_ID \
  --topic=TOPIC_ID \
  --enable-exactly-once-delivery

Haz los cambios siguientes:

  • SUBSCRIPTION_ID: ID de la suscripción que se va a crear.
  • TOPIC_ID: el ID del tema al que se adjunta la suscripción

REST

Para crear una suscripción con entrega exactamente una vez, usa el método projects.subscriptions.create.

PUT https://pubsub.googleapis.com/v1/projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID
Authorization: Bearer $(gcloud auth print-access-token)

Haz los cambios siguientes:

  • PROJECT_ID: el ID del proyecto en el que se va a crear la suscripción
  • SUBSCRIPTION_ID: ID de la suscripción que se va a crear.

Para crear una suscripción de extracción con entrega exactamente una vez, especifica lo siguiente en el cuerpo de la solicitud:

{
  "topic": "projects/PROJECT_ID/topics/TOPIC_ID",
  "enableExactlyOnceDelivery": true,
}

Haz los cambios siguientes:

  • PROJECT_ID: el ID del proyecto que contiene el tema
  • TOPIC_ID: el ID del tema al que se adjunta la suscripción

C++

Antes de probar este ejemplo, sigue las instrucciones de configuración de C++ que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de C++ de Pub/Sub.

namespace pubsub = ::google::cloud::pubsub;
namespace pubsub_admin = ::google::cloud::pubsub_admin;
[](pubsub_admin::SubscriptionAdminClient client,
   std::string const& project_id, std::string const& topic_id,
   std::string const& subscription_id) {
  google::pubsub::v1::Subscription request;
  request.set_name(
      pubsub::Subscription(project_id, subscription_id).FullName());
  request.set_topic(pubsub::Topic(project_id, topic_id).FullName());
  request.set_enable_exactly_once_delivery(true);
  auto sub = client.CreateSubscription(request);
  if (sub.status().code() == google::cloud::StatusCode::kAlreadyExists) {
    std::cout << "The subscription already exists\n";
    return;
  }
  if (!sub) throw std::move(sub).status();

  std::cout << "The subscription was successfully created: "
            << sub->DebugString() << "\n";
}

C#

Antes de probar este ejemplo, sigue las instrucciones de configuración de C# que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de C# de Pub/Sub.


using Google.Cloud.PubSub.V1;
using Grpc.Core;

public class CreateSubscriptionWithExactlyOnceDeliverySample
{
    public Subscription CreateSubscriptionWithExactlyOnceDelivery(string projectId, string topicId, string subscriptionId)
    {
        SubscriberServiceApiClient subscriber = SubscriberServiceApiClient.Create();
        TopicName topicName = TopicName.FromProjectTopic(projectId, topicId);
        SubscriptionName subscriptionName = SubscriptionName.FromProjectSubscription(projectId, subscriptionId);

        var subscriptionRequest = new Subscription
        {
            SubscriptionName = subscriptionName,
            TopicAsTopicName = topicName,
            EnableExactlyOnceDelivery = true
        };

        Subscription subscription = null;

        try
        {
            subscription = subscriber.CreateSubscription(subscriptionRequest);
        }
        catch (RpcException e) when (e.Status.StatusCode == StatusCode.AlreadyExists)
        {
            // Already exists.  That's fine.
        }
        return subscription;
    }
}

Go

En el siguiente ejemplo se usa la versión principal de la biblioteca de cliente de Go Pub/Sub (v2). Si sigues usando la biblioteca v1, consulta la guía de migración a la versión 2. Para ver una lista de ejemplos de código de la versión 1, consulta los ejemplos de código obsoletos.

Antes de probar este ejemplo, sigue las instrucciones de configuración de Go que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API Go de Pub/Sub.

import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/pubsub/v2"
	"cloud.google.com/go/pubsub/v2/apiv1/pubsubpb"
)

func createSubscriptionWithExactlyOnceDelivery(w io.Writer, projectID, topic, subscription string) error {
	// projectID := "my-project-id"
	// topic := "projects/my-project-id/topics/my-topic"
	// subscription := "projects/my-project/subscriptions/my-sub"
	ctx := context.Background()
	client, err := pubsub.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("pubsub.NewClient: %w", err)
	}
	defer client.Close()

	pbSub := &pubsubpb.Subscription{
		Name:                      subscription,
		Topic:                     topic,
		EnableExactlyOnceDelivery: true,
	}
	sub, err := client.SubscriptionAdminClient.CreateSubscription(ctx, pbSub)
	if err != nil {
		return fmt.Errorf("failed to create exactly once sub: %w", err)
	}
	fmt.Fprintf(w, "Created a subscription with exactly once delivery enabled: %v\n", sub)
	return nil
}

Java

Antes de probar este ejemplo, sigue las instrucciones de configuración de Java que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de Java de Pub/Sub.

import com.google.cloud.pubsub.v1.SubscriptionAdminClient;
import com.google.pubsub.v1.ProjectSubscriptionName;
import com.google.pubsub.v1.ProjectTopicName;
import com.google.pubsub.v1.Subscription;
import java.io.IOException;

public class CreateSubscriptionWithExactlyOnceDelivery {
  public static void main(String... args) throws Exception {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "your-project-id";
    String topicId