Entrega exatamente uma vez

Esta página explica como receber e confirmar mensagens usando o recurso de entrega exatamente uma vez do Pub/Sub, que permite rastrear e evitar o processamento duplicado de mensagens. Quando o recurso está ativado, o Pub/Sub oferece a seguinte semântica:

  • Os assinantes podem determinar se os reconhecimentos de mensagens foram bem-sucedidos.

  • Nenhuma nova entrega ocorre depois que a mensagem é confirmada.

  • Nenhuma nova entrega ocorre enquanto uma mensagem está pendente. Uma mensagem é considerada pendente até que o prazo de confirmação expire ou que ela seja confirmada.

  • Em caso de várias entregas válidas, devido à expiração do prazo de confirmação ou à confirmação negativa iniciada pelo cliente, somente o ID de confirmação mais recente pode ser usado para confirmar a mensagem. Todas as solicitações com um ID de confirmação anterior vão falhar.

Com a opção "exatamente uma vez" ativada, os assinantes podem garantir que as mensagens sejam processadas uma vez seguindo estas diretrizes:

  • Confirmar mensagens dentro do prazo.

  • Manter informações sobre o progresso do processamento de uma mensagem até que ela seja confirmada com sucesso.

  • Use as informações sobre o progresso do processamento de uma mensagem para evitar trabalho duplicado quando um reconhecimento falha.

Somente o tipo de assinatura por pull oferece suporte à entrega exatamente uma vez, incluindo assinantes que usam a API StreamingPull. As assinaturas de push e exportação não são compatíveis com a entrega exatamente uma vez.

O Pub/Sub oferece suporte à entrega exatamente uma vez, em uma região da nuvem, com base em um ID de mensagem exclusivo definido pelo Pub/Sub.

Reenvio x duplicação

É importante entender a diferença entre novas entregas esperadas e inesperadas.

  • Uma reentrega pode acontecer devido a uma confirmação negativa de uma mensagem iniciada pelo cliente ou quando o cliente não estende o prazo de confirmação da mensagem antes que ele expire. As novas entregas são consideradas válidas e o sistema funciona como esperado.

    Para resolver problemas de reenvio, consulte Como lidar com duplicados.

  • Uma mensagem é duplicada quando é enviada novamente após uma confirmação bem-sucedida ou antes do vencimento do prazo de confirmação.

  • Uma mensagem reencaminhada mantém o mesmo ID entre as tentativas de reencaminhamento.

As assinaturas com a entrega exatamente uma vez ativada não recebem entregas duplicadas.

Suporte à entrega exatamente uma vez em bibliotecas de cliente

  • As bibliotecas de cliente compatíveis têm uma interface para confirmação com resposta (exemplo: Go). Você pode usar essa interface para verificar se a solicitação de confirmação foi bem-sucedida. Se a solicitação de confirmação for bem-sucedida, os clientes não vão receber uma nova entrega. Se a solicitação de confirmação falhar, os clientes podem esperar uma nova entrega.

  • Os clientes também podem usar as bibliotecas de cliente compatíveis sem a interface de confirmação. No entanto, nesses casos, as falhas de confirmação podem levar a novas entregas silenciosas de mensagens.

  • As bibliotecas de cliente compatíveis têm interfaces para definir o tempo mínimo de extensão do contrato de locação (exemplo: Go). Defina um valor alto para a extensão mínima do contrato de locação para evitar expirações de confirmação relacionadas à rede. O valor máximo é de 600 segundos.

  • Se você estiver usando a biblioteca de cliente Java e inicializar o assinante com um canal gRPC personalizado usando o método setChannelProvider(), recomendamos que você também defina maxInboundMetadataSize como pelo menos 1 MB ao criar o TransportChannelProvider. Para essa configuração, use o método InstantiatingGrpcChannelProvider.Builder.setMaxInboundMetadataSize() ou ManagedChannelBuilder.maxInboundMetadataSize().

Os valores e intervalos padrão das variáveis relacionadas à entrega exatamente uma vez e os nomes das variáveis podem variar entre as bibliotecas de cliente. Por exemplo, na biblioteca de cliente Java, as seguintes variáveis controlam a entrega exatamente uma vez.

Variável Descrição Valor
setEnableExactlyOnceDelivery Ativa ou desativa a entrega exatamente uma vez. true ou false Default=false
minDurationPerAckExtension O tempo mínimo em segundos a ser usado para estender o prazo de confirmação da modificação. Intervalo=0 a 600 Padrão=nenhum
maxDurationPerAckExtension O tempo máximo em segundos para usar na extensão do prazo de confirmação da modificação. Intervalo=0 a 600 Padrão=nenhum

No caso da entrega exatamente uma vez, a solicitação modifyAckDeadline ou acknowledgment para o Pub/Sub falha quando o ID de confirmação já expirou. Nesses casos, o serviço considera o ID de confirmação expirado como inválido, já que uma entrega mais recente pode estar em andamento. Isso ocorre por design para entrega exatamente uma vez. Em seguida, você verá as solicitações acknowledgment e ModifyAckDeadline retornarem uma resposta INVALID_ARGUMENT. Quando a entrega exatamente uma vez está desativada, essas solicitações retornam OK em casos de IDs de confirmação expirados.

Para garantir que as solicitações acknowledgment e ModifyAckDeadline tenham IDs de confirmação válidos, defina o valor de minDurationPerAckExtension como um número alto.

Considerações regionais

A garantia de entrega exatamente uma vez só se aplica quando os assinantes se conectam ao serviço na mesma região. Se o aplicativo assinante estiver distribuído em várias regiões, isso poderá levar à entrega de mensagens duplicadas, mesmo quando a entrega exatamente uma vez estiver ativada. Os editores podem enviar mensagens para qualquer região, e a garantia de entrega única ainda é mantida.

Quando você executa o aplicativo em Trusted Cloud, por padrão ele se conecta ao endpoint do Pub/Sub na mesma região. Portanto, executar o aplicativo em uma única região dentro do Trusted Cloudgeralmente garante que você esteja interagindo com uma única região.

Ao executar o aplicativo de assinante fora do Trusted Cloud ou em várias regiões, é possível garantir a conexão com uma única região usando um endpoint de local ao configurar o cliente do Pub/Sub. Todos os endpoints de local do Pub/Sub apontam para regiões únicas. Para saber mais sobre endpoints regionais, consulte Endpoints do Pub/Sub. Para uma lista de todos os endpoints de localização do Pub/Sub, consulte Lista de endpoints de localização.

Criar assinaturas com entrega exatamente uma vez

É possível criar uma assinatura com entrega exatamente uma vez usando o console Trusted Cloud , a Google Cloud CLI, a biblioteca de cliente ou a API Pub/Sub.

Assinatura de pull

Console

Para criar uma assinatura de pull com entrega exatamente uma vez, siga estas etapas:

  1. No console Trusted Cloud , acesse a página Assinaturas.

    Acessar "Assinaturas"

  2. Clique em Criar assinatura.

  3. Insira o ID da assinatura.

  4. Escolha ou crie um tópico no menu suspenso.

    A assinatura recebe mensagens do tópico.

  5. Na seção Entrega exatamente uma vez, selecione Ativar entrega exatamente uma vez.

  6. Clique em Criar.

gcloud

Para criar uma assinatura de pull com entrega única, use o comando gcloud pubsub subscriptions create com a flag --enable-exactly-once-delivery:

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

Substitua:

  • SUBSCRIPTION_ID: o ID da assinatura a ser criada
  • TOPIC_ID: o ID do tópico a ser anexado à assinatura

REST

Para criar uma assinatura com entrega exatamente uma vez, use o método projects.subscriptions.create.

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

Substitua:

  • PROJECT_ID: o ID do projeto para criar a assinatura
  • SUBSCRIPTION_ID: o ID da assinatura a ser criada

Para criar uma assinatura de pull com entrega exatamente uma vez, especifique isso no corpo da solicitação:

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

Substitua:

  • PROJECT_ID: o ID do projeto com o tópico
  • TOPIC_ID: o ID do tópico a ser anexado à assinatura

C++

Antes de tentar esse exemplo, siga as instruções de configuração do C++ em Guia de início rápido: como usar bibliotecas de cliente. Para mais informações, consulte a documentação de referência da API Pub/Sub C++.

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 tentar esse exemplo, siga as instruções de configuração do C# em Guia de início rápido: como usar bibliotecas de cliente. Para mais informações, consulte a documentação de referência da API Pub/Sub C#.


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

O exemplo a seguir usa a versão principal da biblioteca de cliente do Go Pub/Sub (v2). Se você ainda estiver usando a biblioteca v1, consulte o guia de migração para a v2. Para conferir uma lista de exemplos de código da v1, consulte os exemplos de código descontinuados.

Antes de tentar esse exemplo, siga as instruções de configuração do Go em Guia de início rápido: como usar bibliotecas de cliente. Para mais informações, consulte a documentação de referência da API Pub/Sub Go.

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 tentar essa amostra, siga as instruções de configuração do Java em Guia de início rápido: como usar bibliotecas de cliente. Para mais informações, consulte a documentação de referência da API Pub/Sub Java.

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 = "your-topic-id";
    String subscriptionId = "your-subscription-id";

    createSubscriptionWithExactlyOnceDeliveryExample(projectId, topicId, subscriptionId);
  }

  public static void createSubscriptionWithExactlyOnceDeliveryExample(
      String projectId, String topicId, String subscriptionId) throws IOException {
    try (SubscriptionAdminClient subscriptionAdminClient = SubscriptionAdminClient.create()) {

      ProjectTopicName topicName = ProjectTopicName.of(projectId, topicId);
      ProjectSubscriptionName subscriptionName =
          ProjectSubscriptionName.of(projectId, subscriptionId);

      Subscription subscription =
          subscriptionAdminClient.createSubscription(
              Subscription.newBuilder()
                  .setName(subscriptionName.toString())
                  .setTopic(topicName.toString())
                  // Enable exactly once delivery in the subscription.
                  .setEnableExactlyOnceDelivery(true)
                  .build());

      System.out.println(
          "Created a subscription with exactly once delivery enabled: "
              + subscription.getAllFields());
    }
  }
}

Python

Antes de tentar esse exemplo, siga as instruções de configuração do Python em Guia de início rápido: como usar bibliotecas de cliente. Para mais informações, consulte a documentação de referência da API Pub/Sub Python.

from google.cloud import pubsub_v1

# TODO(developer): Choose an existing topic.
# project_id = "your-project-id"
# topic_id = "your-topic-id"
# subscription_id = "your-subscription-id"

publisher = pubsub_v1.PublisherClient()
subscriber = pubsub_v1.SubscriberClient()
topic_path = publisher.topic_path(project_id, topic_id)
subscription_path = subscriber.subscription_path(project_id, subscription_id)

with subscriber:
    subscription = subscriber.create_subscription(
        request={
            "name": subscription_path,
            "topic": topic_path,
            "enable_exactly_once_delivery": True,
        }
    )
    print(
        f"Created subscription with exactly once delivery enabled: {subscription}"
    )

Node.js

Antes de tentar essa amostra, siga as instruções de configuração do Node.js em Guia de início rápido: como usar bibliotecas de cliente. Para mais informações, consulte a documentação de referência da API Pub/Sub Node.js.

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID';
// const subscriptionNameOrId = 'YOUR_SUBSCRIPTION_NAME_OR_ID';

// Imports the Google Cloud client library
const {PubSub} = require('@google-cloud/pubsub');

// Creates a client; cache this for further use
const pubSubClient = new PubSub();

async function createSubscriptionWithExactlyOnceDelivery(
  topicNameOrId,
  subscriptionNameOrId,
) {
  // Creates a new subscription
  await pubSubClient
    .topic(topicNameOrId)
    .createSubscription(subscriptionNameOrId, {
      enableExactlyOnceDelivery: true,
    });
  console.log(
    `Created subscription ${subscriptionNameOrId} with exactly-once delivery.`,
  );
  console.log(
    'To process messages, remember to check the return value of ackWithResponse().',
  );
}

Node.js

Antes de tentar essa amostra, siga as instruções de configuração do Node.js em Guia de início rápido: como usar bibliotecas de cliente. Para mais informações, consulte a documentação de referência da API Pub/Sub Node.js.

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID';
// const subscriptionNameOrId = 'YOUR_SUBSCRIPTION_NAME_OR_ID';

// Imports the Google Cloud client library
import {PubSub} from '@google-cloud/pubsub';

// Creates a client; cache this for further use
const pubSubClient = new PubSub();

async function createSubscriptionWithExactlyOnceDelivery(
  topicNameOrId: string,
  subscriptionNameOrId: string,
) {
  // Creates a new subscription
  await pubSubClient
    .topic(topicNameOrId)
    .createSubscription(subscriptionNameOrId, {
      enableExactlyOnceDelivery: true,
    });
  console.log(
    `Created subscription ${subscriptionNameOrId} with exactly-once delivery.`,
  );
  console.log(
    'To process messages, remember to check the return value of ackWithResponse().',
  );
}

Ruby

O exemplo a seguir usa a biblioteca de cliente do Ruby Pub/Sub v3. Se você ainda estiver usando a biblioteca v2, consulte o guia de migração para a v3. Para conferir uma lista de exemplos de código do Ruby v2, consulte os exemplos de código descontinuados.

Antes de tentar esse exemplo, siga as instruções de configuração do Ruby em Guia de início rápido: como usar bibliotecas de cliente. Para mais informações, consulte a documentação de referência da API Pub/Sub Ruby.

# project_id = "your-project-id"
# topic_id = "your-topic-id"
# subscription_id = "your-subscription-id"

pubsub = Google::Cloud::PubSub.new project_id: project_id
subscription_admin = pubsub.subscription_admin

subscription = subscription_admin.create_subscription \
  name: pubsub.subscription_path(subscription_id),
  topic: pubsub.topic_path(topic_id),
  enable_exactly_once_delivery: true

puts "Created subscription with exactly once delivery enabled: " \
     "#{subscription_id}"

PHP

Antes de tentar esse exemplo, siga as instruções de configuração do PHP em Guia de início rápido: como usar bibliotecas de cliente. Para mais informações, consulte a documentação de referência da API Pub/Sub PHP.

use Google\Cloud\PubSub\PubSubClient;

/**
 * Creates a Pub/Sub subscription with `Exactly Once Delivery` enabled.
 *
 * @param string $projectId  The Google project ID.
 * @param string $topicName  The Pub/Sub topic name.
 * @param string $subscriptionName  The Pub/Sub subscription name.
 */
function create_subscription_with_exactly_once_delivery(
    string $projectId,
    string $topicName,
    string $subscriptionName
): void {
    $pubsub = new PubSubClient([
        'projectId' => $projectId,
    ]);
    $topic = $pubsub->topic($topicName);
    $subscription = $topic->subscription($subscriptionName);
    $subscription->create([
        'enableExactlyOnceDelivery' => true
    ]);

    // Exactly Once Delivery status for the subscription
    $status = $subscription->info()['enableExactlyOnceDelivery'];

    printf('Subscription created with exactly once delivery status: %s' . PHP_EOL, $status ? 'true' : 'false');
}

Monitorar assinaturas de entrega exatamente uma vez

A métrica subscription/exactly_once_warning_count registra o número de eventos que podem levar a novas entregas (válidas ou duplicadas). Essa métrica conta as vezes em que o Pub/Sub não processa solicitações associadas a IDs de confirmação (solicitação ModifyAckDeadline ou acknowledgment). Os motivos podem ser baseados no servidor ou no cliente. Por exemplo, se a camada de persistência usada para manter as informações de entrega exatamente uma vez não estiver disponível, será um evento baseado no servidor. Se o cliente tentar confirmar uma mensagem com um ID de confirmação inválido, será um evento baseado no cliente.

Entenda a métrica

subscription/exactly_once_warning_count captura eventos que podem ou não levar a novas entregas e pode ser ruidoso com base no comportamento do cliente. Por exemplo, solicitações repetidas de acknowledgment ou ModifyAckDeadline com IDs de confirmação inválidos incrementam a métrica repetidamente.

As métricas a seguir também são úteis para entender o comportamento do cliente:

  • A métrica subscription/expired_ack_deadlines_count mostra o número de expirações de ID de confirmação. O vencimento ID de confirmação pode causar falhas nas solicitações ModifyAckDeadline e acknowledgment.

  • A métrica service.serviceruntime.googleapis.com/api/request_count pode ser usada para capturar falhas de solicitações ModifyAckDeadline ou acknowledgment em casos em que as solicitações chegam ao Trusted Cloud by S3NS , mas não ao Pub/Sub. Há falhas que essa métrica não captura, por exemplo, quando os clientes são desconectados do Trusted Cloud by S3NS.

Na maioria dos casos de eventos de falha que podem ser repetidos, as bibliotecas de cliente compatíveis tentam fazer a solicitação novamente de forma automática.

Cotas

As assinaturas de entrega exatamente uma vez estão sujeitas a requisitos de cota adicionais. Essas cotas são aplicadas em:

  • Número de mensagens consumidas de assinaturas com entrega exatamente uma vez ativada por região.
  • Número de mensagens confirmadas ou com prazo estendido ao usar assinaturas com entrega exatamente uma vez ativada por região.

Para mais informações sobre essas cotas, consulte a tabela no tópico Cotas.

Entrega exatamente uma vez e assinaturas ordenadas

O Pub/Sub oferece suporte à entrega exatamente uma vez com entrega ordenada.

Ao usar a ordenação com entrega única, o Pub/Sub espera que os reconhecimentos estejam em ordem. Se os reconhecimentos estiverem fora de ordem, o serviço vai falhar nas solicitações com erros temporários. Se o prazo de confirmação expirar antes de uma confirmação em ordem para a entrega, o cliente vai receber uma nova entrega da mensagem. Por isso, quando você usa o pedido com entrega exatamente uma vez, a capacidade do cliente é limitada a uma ordem de mil mensagens por segundo.

Entrega exatamente uma vez e assinaturas de push

O Pub/Sub oferece suporte à entrega exatamente uma vez apenas com assinaturas pull.

Os clientes que consomem mensagens das assinaturas de push confirmam as mensagens respondendo às solicitações de push com uma resposta bem-sucedida. No entanto, os clientes não sabem se a assinatura do Pub/Sub recebeu e processou a resposta. Isso é diferente das assinaturas de pull, em que as solicitações de confirmação são iniciadas pelos clientes, e a assinatura do Pub/Sub responde se a solicitação foi processada com êxito. Por isso, a semântica de entrega exatamente uma vez não se alinha bem com as assinaturas de push.

Informações úteis

  • Se o prazo de confirmação não for especificado no momento de CreateSubscription, as assinaturas com entrega exatamente uma vez ativada terão um prazo de confirmação padrão de 60 segundos.

  • Prazos de confirmação padrão mais longos são úteis para evitar nova entrega causada por eventos de rede. As bibliotecas de cliente compatíveis não usam o prazo de confirmação de assinatura padrão.

  • As assinaturas de entrega exatamente uma vez têm uma latência de publicação para inscrição significativamente maior em comparação com as assinaturas regulares.

  • Se você precisar de alta capacidade de processamento, os clientes de entrega exatamente uma vez também precisarão usar o streaming pull.

  • Uma assinatura pode receber várias cópias da mesma mensagem devido a duplicatas do lado da publicação, mesmo com a entrega exatamente uma vez ativada. Duplicatas do lado da publicação podem ocorrer devido a várias novas tentativas de publicação exclusivas pelo cliente de publicação ou pelo serviço Pub/Sub. Várias publicações exclusivas pelo cliente de publicação, em várias tentativas, levam a novas entregas com IDs de mensagens diferentes. Várias publicações exclusivas pelo serviço do Pub/Sub, para responder a uma solicitação de publicação do cliente, levam a reenvios com os mesmos IDs de mensagem.

  • É possível tentar novamente em subscription/exactly_once_warning_count, e as bibliotecas de cliente compatíveis fazem isso automaticamente. No entanto, não é possível repetir falhas relacionadas a IDs de confirmação inválidos.