Como solicitar mensagens

O Cloud Pub/Sub oferece um serviço de entrega de mensagens escalonável e altamente disponível. A desvantagem dessas propriedades é que não há garantia da ordem em que as mensagens são recebidas pelos assinantes. A falta de ordem pode parecer um problema, mas há pouquíssimos casos de uso que realmente exigem uma ordem precisa.

Neste documento, apresentamos uma visão geral do que realmente é a ordem de mensagens, as vantagens e desvantagens dela e uma discussão de casos de uso e técnicas para lidar com mensagens dependentes de ordem no fluxo de trabalho atual durante a migração para o Cloud Pub/Sub.

O que é ordem?

No geral, a ideia de que as mensagens têm uma ordem é simples. A imagem a seguir mostra uma visão panorâmica de como uma mensagem passa por um serviço de entrega de mensagens:

O que é ordem?

Seu editor envia uma mensagem em um tópico do Cloud Pub/Sub. A mensagem é entregue para seu assinante por meio de uma assinatura. Com um único editor síncrono, um único assinante síncrono e um único servidor de entregas síncrono - todos em execução em uma camada de transporte síncrona - a noção de ordem parece simples: as mensagens são ordenadas pela data em que o editor as publica com êxito, por alguma medida de tempo absoluto ou sequência.

Mesmo nesse caso, a garantia de ordem imporia restrições severas à capacidade. A única maneira de realmente garantir a ordem das mensagens seria o serviço entregar uma mensagem por vez ao assinante e só enviar a mensagem seguinte quando souber que ele recebeu e processou a mensagem atual, geralmente por meio de uma confirmação enviada pelo assinante ao serviço. A capacidade de transferência de uma mensagem por vez ao assinante não é escalonável. Em vez disso, o serviço poderia somente garantir que a primeira entrega de qualquer mensagem fosse feita na ordem e permitir novas tentativas a qualquer momento. Isso permitiria o envio de várias mensagens ao assinante de uma só vez. No entanto, mesmo se as restrições de ordem forem mais flexíveis, o conceito de "ordem" faz menos sentido na medida em que você se afasta do caso de um único editor/serviço de entrega de mensagens/assinante.

Minhas mensagens realmente precisam seguir uma ordem específica?

Definir a ordem das mensagens pode ser complicado, dependendo dos seus editores e assinantes. Primeiro, é possível que vários assinantes processem mensagens em uma única assinatura:

Vários assinantes

Nesse caso, mesmo se as assinaturas receberem as mensagens na ordem, não há garantia de que elas serão processadas nessa mesma ordem pelos seus assinantes. Se a ordem de processamento for importante, os assinantes precisarão coordenar as mensagens por meio de um sistema de armazenamento ACID, como o Cloud Datastore ou o Cloud SQL.

Do mesmo modo, ter vários editores no mesmo tópico pode dificultar a ordem:

Vários editores

Como atribuir uma ordem a mensagens publicadas por diferentes editores? Os próprios editores precisam se coordenar ou, então, o serviço de entrega de mensagens precisa anexar uma noção de ordem a cada mensagem recebida. Cada mensagem precisaria incluir a informação de ordem. Essa informação pode ser um timestamp que todos os servidores recebem da mesma origem para evitar problemas de fuso horário ou um número de sequência adquirido de uma única origem com garantias de ACID. Outros sistemas de mensagens que garantem a ordem exigem configurações para que vários editores enviem mensagens por meio de um único servidor a um único assinante.

Nó de serviço de entrega de mensagens expandido

Se o serviço de entrega de mensagens abstrato usado nos exemplos acima fosse um único servidor síncrono, o próprio serviço poderia garantir a ordem. No entanto, um serviço de entrega de mensagens como o Cloud Pub/Sub não é um único servidor, nem em termos de papéis do servidor nem em termos de número de servidores. Na verdade, há camadas entre seus editores e assinantes e o próprio sistema do Cloud Pub/Sub. Este é um diagrama mais detalhado do que acontece quando o Cloud Pub/Sub funciona como o sistema de entrega de mensagens:

Entrega de mensagens

Como você pode ver, uma única mensagem pode tomar vários caminhos para ir do editor ao assinante. A vantagem dessa arquitetura é que ela é altamente disponível, assim, a falha de um único servidor não resulta em um atraso em todo o sistema, além de escalonável, ou seja, as mensagens podem ser distribuídas entre vários servidores para maximizar a capacidade. Os benefícios de sistemas distribuídos como esse foram fundamentais para os produtos do Google, como Pesquisa, Anúncios e Gmail, que se baseiam nos mesmos sistemas que executam o Cloud Pub/Sub.

Como lidar com a ordem?

Agora, você já sabe por que ordenar as mensagens é bastante complexo e por que o Cloud Pub/Sub reduz a ênfase na necessidade da ordem. Para conseguir disponibilidade e escalonabilidade, é importante minimizar a dependência dela. Essa dependência pode tomar diversas formas, cada uma descrita abaixo em alguns casos de uso e soluções comuns.

A ordem não é importante

Casos de uso comuns: fila de tarefas independentes, coleta de estatísticas sobre eventos

Cenários de uso em que a ordem não é importante são perfeitos para o Cloud Pub/Sub. Por exemplo, no caso de tarefas independentes que precisam ser realizadas pelos seus assinantes, cada tarefa é uma mensagem, e o assinante que recebe a mensagem realiza a ação. Como outro exemplo, se você quiser coletar estatísticas sobre todas as ações realizadas pelos clientes no seu servidor, é possível publicar uma mensagem para cada evento e pedir que seus assinantes organizem as mensagens e atualizem os resultados no armazenamento persistente.

A ordem é importante no resultado final

Casos de uso comuns: registros, atualizações de estado

Nos casos de uso dessa categoria, a ordem em que as mensagens são processadas não importa. O importante é que o resultado final esteja na ordem certa. Por exemplo, pense em um registro organizado processado e armazenado em disco. Os eventos do registro vêm de vários editores. Nesse caso, a ordem atual em que os eventos do registro são processados não importa, o importante é que o resultado final possa ser acessado com uma classificação de horário. Assim, você poderia anexar um carimbo de data/hora a cada evento no editor e fazer com que o assinante armazene as mensagens em um armazenamento de dados, como o Cloud Datastore, que permite o armazenamento ou a recuperação pelo carimbo de data/hora classificado.

A mesma opção funciona para atualizações de estado que exigem o acesso somente ao estado mais recente. Por exemplo, pense em como monitorar os preços atuais de diferentes ações quando o importante não for o histórico, apenas o valor mais recente. Você poderia anexar um carimbo de data/hora a cada valor da ação e armazenar apenas os que forem mais recentes que o valor armazenado atualmente.

A ordem das mensagens processadas é importante

Casos de uso comuns: dados de transações cujos limites precisam ser seguidos

A dependência total da ordem em que as mensagens são processadas é o caso mais complicado. Em qualquer solução desse tipo, a ordem estrita das mensagens será feita à custa do desempenho e da capacidade. Você deve depender da ordem apenas quando for absolutamente necessário e quando tiver certeza de que não precisará escalonar para um grande número de mensagens por segundo. Para processar mensagens na ordem, um assinante precisa:

  • conhecer toda a lista de mensagens pendentes e a ordem em que elas precisam ser processadas; ou

  • ter um modo de determinar entre todas as mensagens recebidas se há alguma mensagem que ele ainda não recebeu e que precisa ser processada primeiro.

Para implementar a primeira opção, você pode atribuir um identificador exclusivo a cada mensagem e armazenar a ordem em que as mensagens precisam ser processadas em um local persistente (como o Cloud Datastore). Um assinante verificaria o armazenamento persistente para saber qual é a próxima mensagem a ser processada e garantiria o processamento apenas dessa mensagem. O processamento das outras mensagens recebidas seria colocado em espera até que a ordenação fosse concluída. Nesse caso, é melhor pensar no uso do próprio armazenamento persistente, como a fila de mensagens, em vez de confiar na entrega de mensagens por meio do Cloud Pub/Sub.

Isso é possível com o uso do Cloud Monitoring, que rastreia a métrica pubsub.googleapis.com/subscription/oldest_unacked_message_age. Consulte Métricas compatíveis para ler uma descrição. Um assinante colocaria todas as mensagens temporariamente em um armazenamento persistente e as confirmaria. Ele verificaria periodicamente a data da mensagem mais antiga não confirmada e compararia com os carimbos de data/hora publicados das mensagens no armazenamento. Todas as mensagens publicadas antes da mensagem mais antiga não confirmada foram recebidas, então elas podem ser removidas do armazenamento persistente e processadas na ordem certa.

Como alternativa, se você tiver um único editor síncrono e um único assinante, pode usar um número de sequência para garantir a ordem. Essa abordagem exige o uso de um contador persistente. Em cada mensagem, o editor faz o seguinte:

Node.js

Para ver como criar um cliente do Cloud Pub/Sub, consulte as Bibliotecas de cliente do Cloud Pub/Sub.

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

// Creates a client
const pubsub = new PubSub();

/**
 * TODO(developer): Uncomment the following lines to run the sample.
 */
// const topicName = 'my-topic';
// const data = JSON.stringify({ foo: 'bar' });

// Publishes the message as a string, e.g. "Hello, world!" or JSON.stringify(someObject)
const dataBuffer = Buffer.from(data);

const attributes = {
  // Pub/Sub messages are unordered, so assign an order ID and manually order messages
  counterId: `${getPublishCounterValue()}`,
};

// Publishes the message
const messageId = await pubsub
  .topic(topicName)
  .publish(dataBuffer, attributes);
// Update the counter value
setPublishCounterValue(parseInt(attributes.counterId, 10) + 1);
console.log(`Message ${messageId} published.`);
return messageId;

O assinante faz o seguinte:

Node.js

Para ver como criar um cliente do Cloud Pub/Sub, consulte as Bibliotecas de cliente do Cloud Pub/Sub.

const outstandingMessages = {};

async function listenForOrderedMessages(subscriptionName, timeout) {
  // Imports the Google Cloud client library
  const {PubSub} = require('@google-cloud/pubsub');

  // Creates a client
  const pubsub = new PubSub();

  // References an existing subscription, e.g. "my-subscription"
  const subscription = pubsub.subscription(subscriptionName);

  // Create an event handler to handle messages
  const messageHandler = function(message) {
    // Buffer the message in an object (for later ordering)
    outstandingMessages[message.attributes.counterId] = message;

    // "Ack" (acknowledge receipt of) the message
    message.ack();
  };

  // Listen for new messages until timeout is hit
  subscription.on(`message`, messageHandler);
  await new Promise(r => setTimeout(r, timeout * 1000));
  subscription.removeListener(`message`, messageHandler);

  // Pub/Sub messages are unordered, so here we manually order messages by
  // their "counterId" attribute which was set when they were published.
  const outstandingIds = Object.keys(outstandingMessages).map(counterId =>
    Number(counterId, 10)
  );
  outstandingIds.sort();

  outstandingIds.forEach(counterId => {
    const counter = getSubscribeCounterValue();
    const message = outstandingMessages[counterId];

    if (counterId < counter) {
      // The message has already been processed
      message.ack();
      delete outstandingMessages[counterId];
    } else if (counterId === counter) {
      // Process the message
      console.log(
        `* %d %j %j`,
        message.id,
        message.data.toString(),
        message.attributes
      );
      setSubscribeCounterValue(counterId + 1);
      message.ack();
      delete outstandingMessages[counterId];
    } else {
      // Have not yet processed the message on which this message is dependent
      return false;
    }
  });
}

Qualquer uma dessas soluções causa a latência na publicação e no processamento de mensagens. É preciso haver uma etapa síncrona no editor para criar a ordenação e um atraso em mensagens fora de ordem no assinante e garantir a sequência certa das mensagens.

Resumo

Ao usar um serviço de entrega de mensagens pela primeira vez, entregar as mensagens na ordem parece uma propriedade desejável. Isso simplifica o código necessário para processar mensagens quando a ordem for importante. No entanto, entregar mensagens ordenadas tem um custo muito grande para a disponibilidade e escalonabilidade, independentemente de qual sistema de entrega de mensagens você usa. Para os produtos do Google baseados na mesma infraestrutura no Cloud Pub/Sub, a disponibilidade e a escalonabilidade são recursos de importância vital, e é por isso que o serviço não oferece entrega de mensagens em ordem. Sempre que possível, projete seus aplicativos de modo a evitar a dependência da ordem das mensagens. Assim, você poderá escalonar facilmente, e o escalonamento do Cloud Pub/Sub fará a entrega de todas as mensagens com rapidez e confiança.

Caso decida implementar uma forma de ordenação de mensagens com o Cloud Pub/Sub, consulte as páginas do Cloud Datastore e do Cloud SQL para saber mais sobre a implementação das estratégias descritas neste documento.

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Documentação do Cloud Pub/Sub