Ordenar mensagens

A ordenação de mensagens é uma funcionalidade no Pub/Sub que lhe permite receber mensagens nos seus clientes subscritores pela ordem em que foram publicadas pelos clientes publicadores.

Por exemplo, suponha que um cliente publicador numa região publica as mensagens 1, 2 e 3 por ordem. Com a ordenação de mensagens, o cliente subscritor recebe as mensagens publicadas na mesma ordem. Para serem entregues por ordem, o cliente do publicador tem de publicar as mensagens na mesma região. No entanto, os subscritores podem estabelecer ligação a qualquer região e a garantia de ordenação é mantida.

A ordenação de mensagens é uma funcionalidade útil para cenários como a captura de alterações da base de dados, o acompanhamento da sessão do utilizador e as aplicações de streaming em que a preservação da cronologia dos eventos é importante.

Esta página explica o conceito de ordenação de mensagens e como configurar os clientes subscritores para receber mensagens por ordem. Para configurar os clientes de publicadores para a ordenação de mensagens, consulte o artigo Use chaves de ordenação para publicar uma mensagem.

Vista geral da ordenação de mensagens

A ordenação no Pub/Sub é determinada pelo seguinte:

  • Chave de ordenação: esta é uma string usada nos metadados de mensagens do Pub/Sub e representa a entidade para a qual as mensagens têm de ser ordenadas. A chave de ordenação pode ter um comprimento máximo de 1 KB. Para receber um conjunto de mensagens ordenadas numa região, tem de publicar todas as mensagens com a mesma chave de ordenação na mesma região. Alguns exemplos de chaves de ordenação são os IDs de clientes e a chave primária de uma linha numa base de dados.

    A taxa de transferência de publicação em cada chave de ordenação está limitada a 1 MBps. A taxa de transferência em todas as chaves de ordenação num tópico está limitada à quota disponível numa região de publicação. Este limite pode ser aumentado para muitas unidades de GBps.

    Uma chave de ordenação não é equivalente a uma partição num sistema de mensagens baseado em partições, uma vez que espera-se que as chaves de ordenação tenham uma cardinalidade muito superior à das partições.

  • Ativar ordenação de mensagens: esta é uma definição de subscrição. Quando uma subscrição tem a ordenação de mensagens ativada, os clientes subscritores recebem mensagens publicadas na mesma região com a mesma chave de ordenação pela ordem em que foram recebidas pelo serviço. Tem de ativar esta definição na subscrição.

    Suponhamos que tem duas subscrições A e B associadas ao mesmo tópico T. A subscrição A está configurada com a ordenação de mensagens ativada e a subscrição B está configurada sem a ordenação de mensagens ativada. Nesta arquitetura, as subscrições A e B recebem o mesmo conjunto de mensagens do tópico T. Se publicar mensagens com chaves de ordenação na mesma região, a subscrição A recebe as mensagens pela ordem em que foram publicadas. Por outro lado, a subscrição B recebe as mensagens sem qualquer ordem esperada.

Em geral, se a sua solução exigir que os clientes publicadores enviem mensagens ordenadas e não ordenadas, crie tópicos separados, um para mensagens ordenadas e outro para mensagens não ordenadas.

Considerações sobre a utilização de mensagens ordenadas

A lista seguinte contém informações importantes relativamente ao comportamento das mensagens ordenadas no Pub/Sub:

  • Ordenação dentro da chave: espera-se que as mensagens publicadas com a mesma chave de ordenação sejam recebidas por ordem. Suponha que, para ordenar a chave A, publica as mensagens 1, 2 e 3. Com as encomendas ativadas, espera-se que o artigo 1 seja entregue antes do artigo 2 e que o artigo 2 seja entregue antes do artigo 3.

  • Ordenação entre chaves: não se espera que as mensagens publicadas com diferentes chaves de ordenação sejam recebidas por ordem. Suponhamos que tem as chaves de ordenação A e B. Para a ordenação da chave A, as mensagens 1 e 2 são publicadas por ordem. Para a ordenação da chave B, as mensagens 3 e 4 são publicadas por ordem. No entanto, a mensagem 1 pode chegar antes ou depois da mensagem 4.

  • Reenvio de mensagens: o Pub/Sub envia cada mensagem pelo menos uma vez, pelo que o serviço Pub/Sub pode reenviar mensagens. As retransmissões de uma mensagem acionam a retransmissão de todas as mensagens subsequentes para essa chave, mesmo as mensagens confirmadas. Suponha que um cliente subscritor recebe as mensagens 1, 2 e 3 para uma chave de ordenação específica. Se a mensagem 2 for reenviada (porque o prazo de confirmação expirou ou a confirmação de melhor esforço não persistiu no Pub/Sub), a mensagem 3 também é reenviada. Se a ordenação de mensagens e um tópico de mensagens rejeitadas estiverem ativados numa subscrição, este comportamento pode não ser verdadeiro, uma vez que o Pub/Sub encaminha mensagens para tópicos de mensagens rejeitadas com base no melhor esforço.

  • Atrasos na confirmação e tópicos de mensagens não entregues: as mensagens não confirmadas para uma determinada chave de ordenação podem atrasar potencialmente a entrega de mensagens para outras chaves de ordenação, especialmente durante os reinícios do servidor ou as alterações de tráfego. Para manter a ordem nestes eventos, certifique-se de que confirma a receção de todas as mensagens atempadamente. Se não for possível o reconhecimento atempado, considere usar um tópico de mensagens não entregues para evitar a retenção indefinida de mensagens. Tenha em atenção que a ordem pode não ser preservada quando as mensagens são escritas num tópico de mensagens rejeitadas.

  • Afinidade de mensagens (clientes streamingPull): as mensagens para a mesma chave são normalmente entregues ao mesmo cliente subscritor streamingPull. A afinidade é esperada quando existem mensagens pendentes para uma chave de ordenação para um cliente subscritor específico. Se não existirem mensagens pendentes, a afinidade pode mudar para o equilíbrio de carga ou as desconexões do cliente.

    Para garantir um processamento sem problemas, mesmo com potenciais alterações de afinidade, é fundamental conceber a sua aplicação streamingPull de forma a poder processar mensagens em qualquer cliente para uma determinada chave de ordenação.

  • Integração com o Dataflow: não ative a ordenação de mensagens para subscrições quando configurar o Dataflow com o Pub/Sub. O Dataflow tem o seu próprio mecanismo para ordenar as mensagens na totalidade, o que garante a ordem cronológica em todas as mensagens como parte das operações de janelas. Este método de ordenação difere da abordagem baseada em chaves do Pub/Sub. A utilização de chaves de ordenação com o Dataflow pode reduzir potencialmente o desempenho do pipeline.

  • Dimensionamento automático: o fornecimento ordenado do Pub/Sub é dimensionado para milhares de milhões de chaves de ordenação. Um número maior de chaves de ordenação permite um fornecimento mais paralelo aos subscritores, uma vez que a ordenação se aplica a todas as mensagens com a mesma chave de ordenação.

  • Compromissos de desempenho: a entrega ordenada implica alguns compromissos. Em comparação com a entrega não ordenada, a entrega ordenada diminui a disponibilidade de publicação e aumenta a latência de entrega de mensagens ponto a ponto. No caso de entrega ordenada, a comutação por falha requer coordenação para garantir que as mensagens são escritas e lidas pela ordem correta.

  • Tecla de atalho: quando usa a ordenação de mensagens, todas as mensagens com a mesma chave de ordenação são enviadas para o cliente subscritor na ordem em que são recebidas pelo serviço. A repetição de chamada do utilizador não é executada até que a repetição de chamada seja concluída para a mensagem anterior. A taxa de transferência máxima para mensagens que partilham a mesma chave de ordenação quando são entregues aos subscritores não é limitada pelo Pub/Sub , mas sim pela velocidade de processamento do cliente subscritor. Uma tecla de atalho ocorre quando se acumula um backlog numa chave de ordenação individual porque o número de mensagens produzidas por segundo excede o número de mensagens que o subscritor pode processar por segundo. Para mitigar as teclas de atalho, use as teclas mais detalhadas possíveis e minimize o tempo de processamento por mensagem. Também pode monitorizar a métrica subscription/oldest_unacked_message_age para um valor crescente, o que pode indicar uma palavra-chave popular.

Para mais informações sobre como usar a ordenação de mensagens, consulte os seguintes tópicos de práticas recomendadas:

Comportamento do cliente subscritor para a ordenação de mensagens

Os clientes subscritores recebem mensagens pela ordem em que foram publicadas numa região específica. O Pub/Sub suporta diferentes formas de receber mensagens, como clientes subscritores ligados a subscrições de obtenção e envio. As bibliotecas cliente usam streamingPull (com exceção do PHP).

Para saber mais acerca destes tipos de subscrições, consulte o artigo Escolha um tipo de subscrição.

As secções seguintes abordam o que significa receber mensagens por ordem para cada tipo de cliente subscritor.

Clientes subscritores StreamingPull

Quando usar as bibliotecas de cliente com streamingPull, tem de especificar uma função de retorno de chamada do utilizador que é executada sempre que um cliente subscritor recebe uma mensagem. Com as bibliotecas cliente, para qualquer chave de ordenação, a chamada de retorno é executada até à conclusão nas mensagens pela ordem correta. Se as mensagens forem confirmadas nessa chamada de retorno, todos os cálculos numa mensagem ocorrem por ordem. No entanto, se a chamada de retorno do utilizador agendar outro trabalho assíncrono em mensagens, o cliente subscritor tem de garantir que o trabalho assíncrono é feito por ordem. Uma opção é adicionar mensagens a uma fila de trabalho local que é processada por ordem.

Extraia clientes subscritores

Para clientes subscritores ligados a subscrições de obtenção, a ordenação de mensagens do Pub/Sub suporta o seguinte:

  • Todas as mensagens para uma chave de ordenação na PullResponse estão na ordem adequada na lista.

  • Só pode haver um lote de mensagens pendente para uma chave de ordenação de cada vez.

O requisito de que apenas um lote de mensagens pode estar pendente de cada vez é necessário para manter a entrega ordenada, uma vez que o serviço Pub/Sub não pode garantir o êxito nem a latência da resposta que envia para o pedido de obtenção de um subscritor.

Envio de clientes subscritores

As restrições de envio são ainda mais rigorosas do que as de obtenção. Para uma subscrição push, o Pub/Sub suporta apenas uma mensagem pendente para cada chave de ordenação de cada vez. Cada mensagem é enviada para um ponto final de envio como um pedido separado. Assim, o envio dos pedidos em paralelo teria o mesmo problema que a entrega de vários lotes de mensagens para a mesma chave de ordenação para obter subscritores em simultâneo. As subscrições push podem não ser uma boa escolha para tópicos em que as mensagens são publicadas com frequência com a mesma chave de ordenação ou em que a latência é extremamente importante.

Exporte clientes subscritores

A exportação de subscrições suporta mensagens ordenadas. Para subscrições do BigQuery, as mensagens com a mesma chave de ordenação são escritas na respetiva tabela do BigQuery por ordem. Para subscrições do Cloud Storage, as mensagens com a mesma chave de ordenação podem não ser todas escritas no mesmo ficheiro. Quando estão no mesmo ficheiro, as mensagens para uma chave de ordenação estão por ordem. Quando estão distribuídas por vários ficheiros, as mensagens posteriores de uma chave de ordenação podem aparecer num ficheiro com um nome que tenha uma data/hora anterior à data/hora no nome do ficheiro com as mensagens anteriores.

Ative a encomenda por mensagem

Para receber as mensagens por ordem, defina a propriedade de ordenação de mensagens na subscrição a partir da qual recebe mensagens. A receção de mensagens por ordem pode aumentar a latência. Não pode alterar a propriedade de ordenação de mensagens depois de criar uma subscrição.

Pode definir a propriedade de ordenação de mensagens quando cria uma subscrição através da Google Cloud consola, da CLI do Google Cloud ou da API Pub/Sub.

Consola

Para criar uma subscrição com a propriedade de ordenação de mensagens, siga estes passos:

  1. Na Google Cloud consola, aceda à página Subscrições.

Aceder a Subscrições

  1. Clique em Criar subscrição.

  2. Introduza um ID da subscrição.

  3. Escolha um tópico do qual quer receber mensagens.

  4. Na secção Ordenação de mensagens, selecione Ordenar mensagens com uma chave de ordenação.

  5. Clique em Criar.

gcloud

Para criar uma subscrição com a propriedade de ordenação de mensagens, use o comando gcloud pubsub subscriptions create e o sinalizador --enable-message-ordering:

gcloud pubsub subscriptions create SUBSCRIPTION_ID \
  --enable-message-ordering

Substitua SUBSCRIPTION_ID pelo ID da subscrição.

Se o pedido for bem-sucedido, a linha de comandos apresenta uma confirmação:

Created subscription [SUBSCRIPTION_ID].

REST

Para criar uma subscrição com a propriedade de ordenação de mensagens, envie um pedido como o seguinte:PUT

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

Substitua o seguinte:

  • PROJECT_ID: o ID do projeto do projeto com o tópico
  • SUBSCRIPTION_ID: o ID da subscrição

No corpo do pedido, especifique o seguinte:

{
  "topic": TOPIC_ID,
  "enableMessageOrdering": true,
}

Substitua TOPIC_ID pelo ID do tópico a anexar à subscrição.

Se o pedido for bem-sucedido, a resposta é a subscrição no formato JSON:

{
  "name": projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID,
  "topic": projects/PROJECT_ID/topics/TOPIC_ID,
  "enableMessageOrdering": true,
}

C++

Antes de experimentar este exemplo, siga as instruções de configuração do C++ no artigo Início rápido: usar bibliotecas de cliente. Para mais informações, consulte a documentação de referência da API C++ do 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_message_ordering(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 experimentar este exemplo, siga as instruções de configuração do C# em Início rápido: usar bibliotecas de cliente. Para mais informações, consulte a documentação de referência da API C# do Pub/Sub.


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

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

        var subscriptionRequest = new Subscription
        {
            SubscriptionName = subscriptionName,
            TopicAsTopicName = topicName,
            EnableMessageOrdering = 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;
    }
}

Ir

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

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

import (
	"context"
	"fmt"
	"io"

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

func createWithOrdering(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()

	// Message ordering can only be set when creating a subscription.
	sub, err := client.SubscriptionAdminClient.CreateSubscription(ctx, &pubsubpb.Subscription{
		Name:                  subscription,
		Topic:                 topic,
		EnableMessageOrdering: true,
	})
	if err != nil {
		return fmt.Errorf("CreateSubscription: %w", err)
	}
	fmt.Fprintf(w, "Created subscription: %v\n", sub)
	return nil
}

Java

Antes de experimentar este exemplo, siga as instruções de configuração do Java no artigo Início rápido: usar bibliotecas cliente. Para mais informações, consulte a documentação de referência da API Java do 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 CreateSubscriptionWithOrdering {
  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";

    createSubscriptionWithOrderingExample(projectId, topicId, subscriptionId);
  }

  public static void createSubscriptionWithOrderingExample(
      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())
                  // Set message ordering to true for ordered messages in the subscription.
                  .setEnableMessageOrdering(true)
                  .build());

      System.out.println("Created a subscription with ordering: " + subscription.getAllFields());
    }
  }
}

Node.js

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

/**
 * 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 createSubscriptionWithOrdering(
  topicNameOrId,
  subscriptionNameOrId,
) {
  // Creates a new subscription
  await pubSubClient
    .topic(topicNameOrId)
    .createSubscription(subscriptionNameOrId, {
      enableMessageOrdering: true,
    });
  console.log(
    `Created subscription ${subscriptionNameOrId} with ordering enabled.`,
  );
  console.log(
    'To process messages in order, remember to add an ordering key to your messages.',
  );
}

Node.js

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

/**
 * 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 createSubscriptionWithOrdering(
  topicNameOrId: string,
  subscriptionNameOrId: string,
) {
  // Creates a new subscription
  await pubSubClient
    .topic(topicNameOrId)
    .createSubscription(subscriptionNameOrId, {
      enableMessageOrdering: true,
    });
  console.log(
    `Created subscription ${subscriptionNameOrId} with ordering enabled.`,
  );
  console.log(
    'To process messages in order, remember to add an ordering key to your messages.',
  );
}

Python

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

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_message_ordering": True,
        }
    )
    print(f"Created subscription with ordering: {subscription}")

Ruby

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

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

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

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

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

puts "Pull subscription #{subscription_id} created with message ordering."

O que se segue?