Como implantar sistemas alimentados por eventos com o Cloud Spanner

Com um padrão de arquitetura como exemplo, você aprenderá a usar o Cloud Spanner como um sistema de ingestão de eventos e o Cloud Pub/Sub como um livro de eventos para criar um sistema que pode executar as seguintes tarefas:

  • Escrever para uma fonte de evento que esteja altamente disponível.
  • Publicar essas gravações como eventos para outros sistemas usarem.
  • Arquivar eventos para reprodução.
  • Carregar eventos em um sistema para análise.
  • Filtrar eventos em um sistema para consulta rápida.

Este artigo foi elaborado para engenheiros de software interessados em aprender sobre os usos, as compensações e os componentes de um sistema alimentado por eventos. Aprenda a criar uma série de aplicativos e serviços usando o Cloud Functions para oferecer suporte à sua arquitetura alimentada por eventos.

Casos de uso e critérios de projeto

É possível usar esse padrão de arquitetura sempre que precisar realizar uma ação que envolva gravação de novos dados em uma fonte de dados, neste caso, o Cloud Spanner.

Casos de uso

Esse padrão é útil para gerenciar os seguintes cenários:

  • Carrinhos de compras de comércio eletrônico
  • Gerenciamento de pedidos e cadeia de suprimentos
  • Carteiras, pagamentos e resoluções de cobrança

O diagrama a seguir ilustra o fluxo de um sistema de carrinho de compras de comércio eletrônico.

O fluxo de eventos em um carrinho de compras de comércio eletrônico.

Em sistemas complexos, como comércio eletrônico e pagamentos, é útil rastrear transações usando arquiteturas baseadas em eventos. Por exemplo, quando um cliente adiciona um item a um carrinho de compras ou processa um cartão de crédito para pagamento, convém acionar diversos processos de downstream para verificar se o item está em estoque e se há fundos na conta do cliente. É importante certificar-se de que os clientes podem fazer pedidos a qualquer momento porque sua empresa depende disso.

Critérios de design

Veja a seguir exemplos de critérios de design que sugerem o uso de uma arquitetura de sistema alimentada por eventos:

  • Operação de pedidos e pagamentos para sistemas com alta disponibilidade
  • Verificação se os clientes recebem os itens pedidos e foram cobrados o valor correto pela compra
  • Modos de falha determinísticos, isto é, aqueles em que você tem certeza de que a gravação falhou ou foi bem-sucedida
  • Inclusão de um mecanismo para notificar os serviços dependentes em que um sistema gravou que estavam interessados

Neste artigo, você verá a descrição de um sistema que satisfaz todos esses requisitos e oferece flexibilidade para adicionar funcionalidade posteriormente.

Arquitetura do sistema

Primeiro você precisa de um serviço que aceite gravações com alta disponibilidade. Esse sistema também precisa fornecer modos de falha determinísticos. Ele precisa saber quando uma gravação falha para que a gravação possa ser repetida sem medo de duplicar toda ou parte dela.

O aplicativo canônico para esse cenário é um banco de dados que aceita transações de atomicidade, consistência, isolamento e durabilidade (ACID). No entanto, tornar os bancos de dados altamente disponíveis, especialmente para gravações, é difícil. A replicação pode causar inconsistências nos dados e pode adicionar custo e complexidade à sua arquitetura. A complexidade dos compostos é o maior risco quando a alta disponibilidade é uma prioridade.

Além disso, as configurações de banco de dados de alta disponibilidade típicas não podem processar falhas em zonas de disponibilidade sem causar atrasos de replicação. À medida que esses atrasos aumentam, aumenta a probabilidade de ocorrência de falhas que podem levar à perda de todas as gravações que estão em trânsito para o nó de réplica em outra zona de disponibilidade. Essas outras falhas significam que as gravações não são gravadas por completo na réplica antes da falha nessa zona.

Diagrama da arquitetura

No diagrama a seguir, veja uma arquitetura alimentada por eventos com o Cloud Spanner projetado para tratar dos problemas de complexidade e custo associados aos bancos de dados de alta disponibilidade tradicionais.

Diagrama de arquitetura alimentada por eventos com o Cloud Spanner.

Essa arquitetura depende dos componentes a seguir, que você cria com o Cloud Functions. Esses componentes consistem em aplicativos e no Cloud Functions.

  • Aplicativo de pesquisa: pesquisa no Cloud Spanner, converte o formato de registro para Avro e publica no Cloud Pub/Sub.
  • archiver: recebe eventos acionados por mensagens publicadas em um tópico do Cloud Pub/Sub e grava esses registros em um arquivo global no Cloud Storage.
  • bqloader: é acionado por registros gravados no Cloud Storage e carrega esses registros em uma tabela correspondente do BigQuery.
  • janitor: lê todas as entradas gravadas no arquivo global a uma taxa fixa e, em seguida, compacta-as para armazenamento a longo prazo.
  • replayer: lê os registros do armazenamento de longo prazo em ordem, descomprime e carrega-os em um novo fluxo do Cloud Pub/Sub.
  • Aplicativo materializador: filtra os registros gravados no Cloud Pub/Sub e, em seguida, carrega-os em um banco de dados correspondente do Redis (visualização materializada) para fácil acesso à consultas.

Como criar um serviço de pesquisa

Depois que você tiver um sistema que aceite gravações, seus serviços de downstream precisarão ser notificados toda vez que algo for gravado no sistema.

Os bancos de dados tradicionais fazem isso de maneiras diferentes, mas geralmente com alguma variação de detecção do registro em tempo de gravação (WAL, na sigla em inglês) ou do fluxo de captura de dados alterados (CDC, na sigla em inglês) de um banco de dados. Essas soluções não estão em um formato que facilita a leitura. Ele foi projetado para representar e transmitir as alterações feitas no registro, e não para informar um sistema de eventos de downstream e transmitir o contexto relevante sobre esse evento. Uma representação binária que mostre apenas mudanças, e não um registro completo, não é útil na maioria das situações. Outra desvantagem desse formato é que ele não pode ser lido por humanos, o que dificulta a depuração e a auditoria do fluxo.

Em vez disso, é possível criar algo que pesquise todas as novas entradas no banco de dados e, em seguida, as passa para o sistema de downstream. Os serviços de pesquisa são uma maneira comum de processar novos registros gravados em um banco de dados e contam com as seguintes vantagens:

  • São simples de entender e gravar.
  • Apresentam baixa sobrecarga quando a gravação é feita corretamente.
  • São estáveis e independentes.
  • São tolerantes a erros, tanto de consultas quanto de análise de registro.
  • São flexíveis na forma como as alterações são filtradas e representadas.

Confira abaixo as desvantagens de usar um serviço de pesquisa em vez do WAL ou CDC convencional:

  • A pesquisa do banco de dados em um curto intervalo de tempo (menos de um segundo) pode adicionar carga ao banco de dados.
  • Dependendo do layout da tabela, da consulta que você usa para pesquisa e de quantos outros aplicativos estão pesquisando em seu banco de dados no momento, um serviço de pesquisa pode criar uma contenção de recursos (bloqueios) com outros aplicativos.
  • A pesquisa pode exigir que você use máquinas de banco de dados maiores e armazenamento mais caro (como SSD) para lidar com o volume extra e a contenção de recursos.

É possível atenuar algumas delas no Cloud Spanner usando transações somente leitura para suas leituras de pesquisa. Além disso, verifique se você está seguindo as práticas recomendadas do SQL para consultas eficientes e eficazes.

Para encontrar todos os novos registros de um determinado período, é possível usar o recurso do carimbo de data/hora da confirmação no Cloud Spanner. O carimbo de data/hora da confirmação usa a tecnologia TrueTime para fornecer ao Cloud Spanner uma representação globalmente consistente de quando uma gravação foi confirmada no banco de dados. O Cloud Spanner aceita gravações em muitas regiões do mundo e seu aplicativo de pesquisa cria um registro com a contabilidade precisa dos eventos em ordem.

Como usar o Cloud Functions para representar tarefas

O Cloud Functions é uma plataforma de computação baseada em eventos e sem servidor no GCP. Essas funções são snippets de código sem estado que são executados em resposta a um gatilho, como uma solicitação HTTP ou um gatilho de evento. No caso de sistemas alimentados por eventos, o Cloud Functions geralmente representa as tarefas individuais associadas a um evento que está sendo publicado. Como essas tarefas não têm servidor, elas são escalonadas com o volume de solicitações e não exigem mais intervenções operacionais.

Confira abaixo algumas desvantagens do Cloud Functions:

  • Os tempos de resposta podem ser inconsistentes.
  • A geração de registros e o rastreamento podem ser mais difíceis devido à efemeridade da inicialização e da execução.
  • Precisa ser sem estado e idempotente porque a repetição ao ocorrer uma falha geralmente é automática.
  • Pode ser difícil depurar e reproduzir erros localmente, dependendo do estado do seu ambiente de geração de registros.

Como usar o Cloud Pub/Sub como livro-razão

Um livro-razão é um registro somente de eventos publicados em um barramento de eventos ou em uma fila de mensagens. É possível se inscrever ou detectar um evento ao assinar um determinado barramento de eventos ou um tópico de fila de mensagens. Também é possível filtrar a coleção de todas as mensagens pelo substantivo, verbo ou metadados do seu interesse.

Nesse padrão, o livro-razão é um único tópico do Cloud Pub/Sub. É possível usar o sistema de eventos para acionar o Cloud Functions sempre que um registro for gravado no fluxo.

Certifique-se de que seus inscritos não confirmem a mensagem para que ela continue disponível para outras funções interessadas. Além disso, há uma janela de retenção de mensagens limitada na sua fila. Você precisa criar uma Função do Cloud para fazer backup dessas mensagens em um arquivo. Para acessar as mensagens arquivadas, use uma Função do Cloud para ler e publicá-las em um novo tópico do Cloud Pub/Sub.

Como usar um aplicativo de pesquisa para consultar o banco de dados

A função do aplicativo de pesquisa é consultar o banco de dados em um intervalo de tempo fixo e solicitar todos os registros que ocorrem após um determinado ponto, de acordo com o carimbo de data/hora da confirmação e em ordem decrescente (primeiro registro mais antigo).

Requisitos do aplicativo de pesquisa

Com esse design, você precisa saber o último carimbo de data/hora que o aplicativo pesquisou e precisa inicializar o sistema para a primeira execução. Acompanhe o carimbo de data e hora armazenando-o no estado do aplicativo, gravando-o em outro banco de dados ou solicitando ao livro-razão o registro mais recente e fazendo a análise a partir dele.

Armazenar o registro em outro banco de dados pode causar um aumento na complexidade: é criada uma dependência em outro sistema que adiciona outro ponto de falha. É melhor manter o carimbo de data/hora do último processo no armazenamento local do aplicativo e recorrer à consulta ao livro-razão apenas se o aplicativo falhar ou for reiniciado por algum motivo e o estado interno for perdido.

Os requisitos para o aplicativo de pesquisa são:

  • intervalo de pesquisa fixo e consistente;
  • intervalo de pesquisa inferior a 1 segundo;
  • inicialização do sistema na primeira vez em que é executado;
  • consulta a todos os registros que ocorreram após o carimbo de data/hora gravado anteriormente;
  • serialização de cada registro em um registro Avro individual;
  • publicação de cada registro Avro em um livro-razão de eventos baseado no Cloud Pub/Sub;
  • suspensão até a decorrência do seu intervalo de pesquisa;
  • nova tentativa automática dentro do intervalo de tempo fixo, em caso de falha;
  • na reinicialização do aplicativo de pesquisa, será consultado o fluxo do Cloud Pub/Sub para conseguir o último carimbo de data/hora, em caso de falha. Também é possível iniciar o aplicativo de pesquisa com um carimbo de data/hora configurado manualmente. Se preferir, consiga o carimbo nos arquivos do Cloud Storage.

Como projetar o aplicativo de pesquisa

Em seguida, você projeta o aplicativo de pesquisa. Há, pelo menos, três maneiras diferentes de criá-lo, cada uma com suas vantagens.

É possível usar o Cloud Scheduler para agendar uma Função do Cloud para pesquisar no banco de dados, iniciá-lo em um cluster do Kubernetes como cron job e programá-lo no intervalo desejado. Também é possível executar um serviço continuamente em um pod do Kubernetes e pesquisar o banco de dados com um atraso fixo, mantendo e atualizando o último carimbo de data/hora processado na memória.

Sabendo que você precisa consultar o banco de dados em um intervalo de tempo fixo, pense em usar o Cloud Scheduler para agendar uma Função do Cloud para pesquisar no banco de dados e, em seguida, enviar os novos registros para um fluxo do Cloud Pub/Sub.

Essa abordagem funciona, mas envolve duas desvantagens. Primeiro, você precisa descobrir uma maneira de preservar o estado do aplicativo porque as Funções do Cloud são sem estado por definição. Preservar o estado do aplicativo envolve a criação de outro banco de dados e adiciona algumas inconsistências na latência entre os eventos que estão sendo gravados e os eventos que estão sendo adicionados ao livro-razão. Às vezes, o Cloud Functions pode levar mais tempo na geração e execução do que o esperado. Esse atraso se torna mais evidente quanto menor o intervalo de pesquisa.

Se todos os consumidores previstos do downstream puderem tolerar uma latência variável que pode exceder um segundo, uma Função do Cloud programada poderá ser a melhor opção para o projeto do seu sistema. Nesse caso, a Função do Cloud rastreia o último carimbo de data/hora processado ao consultar o fluxo do Cloud Pub/Sub ou mantém esse estado no Cloud Storage. Ao reduzir a complexidade de gerenciamento de algo como o Kubernetes, uma desvantagem a ser considerada no caso da Função do Cloud é que essas consultas aumentam a latência devido à chamada para o carimbo de data/hora. Dessa forma, elas podem adicionar mais um ponto de falha ao seu sistema de pesquisa. Se for possível tolerar essa latência de chamada, use uma Função do Cloud.

Outra opção é iniciar o aplicativo de pesquisa em um cluster do Kubernetes como um cron job e programá-lo no intervalo desejado. Infelizmente, essa abordagem tem desvantagens semelhantes ao uso do Cloud Scheduler, além da complexidade adicionada pelo Kubernetes. No entanto, é possível iniciar o job como um serviço no Kubernetes e suspendê-lo por um intervalo definido, que você controla no loop do evento principal do aplicativo. É possível manter o estado e ter controle total sobre a latência de pesquisa e a semântica de repetição. Essa escolha envolve uma complexidade extra devido ao Kubernetes, mas é possível atenuá-la com o Google Kubernetes Engine (GKE). Ele fornece o controle máximo sobre:

  • o estado entre as pesquisas (último carimbo de data/hora);
  • a latência entre as pesquisas;
  • novas tentativas de semântica e duração se a leitura do Cloud Spanner ou a gravação do Cloud Pub/Sub falharem;
  • reinicialização automática do serviço de pesquisa se ele falhar ou fechar inesperadamente.

Como inicializar o aplicativo de pesquisa

Na primeira vez que você executa o aplicativo de pesquisa, ele configura todos os componentes necessários para executar o sistema alimentado por evento. Isso inclui o fluxo do Cloud Pub/Sub com o nome correto (idealmente, o nome da tabela) e o intervalo do Cloud Storage para o archiver publicar. Depois que todos os componentes do sistema estiverem configurados, o processo de inicialização do aplicativo de pesquisa fará uma verificação inicial da tabela e processará todos os dados atuais. Ao terminar, ele passa para o intervalo de pesquisa fixo e envia o último carimbo de data/hora de confirmação processado para o sistema de pesquisa usar em sua primeira execução de processamento.

Como usar um livro-razão para interpretar dados

Depois que você tiver o aplicativo de pesquisa agendado e ele estiver puxando os dados mais recentes, você precisará representar esses dados no livro-razão. Como essa é uma solução generalizada (ou seja, é possível reutilizá-la para muitas tabelas com esquemas diferentes), a criação de aplicativos de pesquisa específicos de tabela e de livro-razão não é uma solução escalonável. Você também precisa ser capaz de adicionar diferentes consumidores ao livro-razão sem que eles precisem conhecer as variações do esquema com versões anteriores.

Veja alguns casos de uso possíveis:

  • Arquivamento a longo prazo de todas as transações
  • Carregamento de transações em um armazenamento de dados para análise
  • Carregamento de transações em um banco de dados NoSQL para alimentar modelos de machine learning e possivelmente armazenar em cache respostas a perguntas frequentes

Para esses casos de uso, pense em usar um formato de serialização, como Avro, JSON ou Protobuf. O BigQuery, a solução de armazenamento de dados do Google, é compatível com ingestão de dados diretamente de arquivos Avro. O Avro é o formato preferido para carregar dados no BigQuery. Carregar esses arquivos Avro tem as seguintes vantagens sobre o JSON:

  • Carregamento mais rápido. Os dados podem ser lidos em paralelo, mesmo que os blocos de dados estejam compactados.
  • Não requer digitação ou serialização.
  • São mais fáceis de analisar porque não há problemas inerentes de codificação encontrados em outros formatos.

Ao carregar arquivos Avro no BigQuery, o esquema da tabela é inferido automaticamente dos dados de origem autodescritivos.

O Protobuf é uma alternativa ao Avro, mas para este caso de uso o Avro apresenta duas vantagens:

  • Compatibilidade com a ingestão direta no BigQuery
  • Esquema contido no objeto de dados

A última vantagem permite que os consumidores do livro-razão extraiam e inspecionem os dados. Eles não precisam desserializar o JSON e esperar que o formato não seja alterado, nem receber uma versão de um registro de esquema para uma determinada versão desse objeto.

Por isso, serialize as tabelas do Cloud Spanner em um objeto Avro para cada transação antes de colocá-la no livro-razão. Para mais informações, consulte como transformar uma tabela do Cloud Spanner em um registro Avro.

Como escrever o aplicativo de pesquisa

Depois de decidir sobre o modelo de implantação e o formato de serialização, pensa nas opções de linguagem para escrever o aplicativo de pesquisa. Como o objetivo é ler dados do Cloud Spanner e gravá-los no Cloud Pub/Sub, você está limitado às linguagens compatíveis com as APIs do Google Cloud: C#, Go, Java, Node.js, PHP, Python e Ruby.

Das linguagens compatíveis com o Cloud Spanner, o Avro é oficialmente compatível com C#, Java, Python, PHP e Ruby. Para ter o máximo de controle possível sobre a latência do seu aplicativo e processar as tabelas consultadas em vários segmentos, o Java é uma boa opção.

Como usar o fluxo de eventos

O aplicativo de pesquisa é o principal no serviço, mas há vários outros consumidores do fluxo.O primeiro aplicativo de que você precisa é um que se inscreva no fluxo e arquive cada mensagem no Cloud Storage para referência de histórico. Esse sistema armazena cada registro como um arquivo separado em um intervalo com o mesmo nome da tabela.

A cada hora (ou na sua frequência de transação), há outro serviço que pega esses registros de transações individuais e os compacta em um arquivo maior. Dependendo da frequência e do tamanho de seus registros de transação, talvez convenha compactar os arquivos Avro de transação individual.

Depois de ter todas as suas transações arquivadas no Cloud Storage, é possível:

  • preencher diretamente o BigQuery com dados de transação para análise;
  • reproduzir registros históricos para treinamento ou teste de outros sistemas;
  • recriar o conteúdo do sistema de registro (o banco de dados do Cloud Spanner, neste caso), caso ele seja perdido ou corrompido;
  • criar uma réplica histórica somente leitura para relatórios ou auditoria;
  • criar uma versão do banco de dados para ambientes de preparo ou teste.

Como criar uma Função do Cloud archiver

Crie uma Função do Cloud archiver que será acionada sempre que uma transação for adicionada ao fluxo. Você precisa criar uma nova função e acionar por tópico (uma tabela no Cloud Spanner). Toda vez que uma transação é adicionada ao tópico do Cloud Pub/Sub correspondente, a Função archiver pega o registro Avro e o grava em um intervalo do Cloud Storage com o mesmo nome da tabela. A Função do Cloud nomeia o arquivo, captura a data e a hora até o milissegundo, além de mais quatro dígitos aleatórios. Esse esquema de nomenclatura cria um ID semelhante a um identificador único universal (UUID, na sigla em inglês).

Como criar a Função do Cloud bqloader

Agora é possível criar uma nova Função do Cloud chamada bqloader para carregar o arquivo Avro diretamente no BigQuery. A função é acionada quando a archiver faz o upload do arquivo para o Cloud Storage. Toda vez que uma transação é carregada no Cloud Storage, essa Função do Cloud anexa a entrada à tabela correta do BigQuery. Se você quiser reduzir ainda mais a latência de itens como análise ou alimentação de modelos de machine learning, é possível fazer streaming dos seus dados para o BigQuery, um registro por vez, com o método tabledata.insertAll. Essa abordagem permite a consulta de dados sem o atraso de execução de um job de carga. Tenha sempre as cotas para o carregamento de registros no BigQuery em mente.

Como criar a Função do Cloud janitor

A Função do Cloud archiver grava o arquivo Avro em um intervalo do Cloud Storage. Em seguida, você cria outra Função do Cloud, chamada janitor, para compactar todas as transações individuais em um único arquivo compactado para armazenamento a longo prazo. janitor nomeia o arquivo recém-criado usando o nome da tabela, a data e o período incluídos nos arquivos. Por exemplo, se janitor estiver programada para ser executado a cada hora, o nome do arquivo talvez seja table1-jan_1_2019_1200-1300.tar.gz. A Função janitor não é um requisito, mas ajuda a manter os custos de armazenamento baixos e os intervalos do Cloud Storage organizados.

Como criar a Função do Cloud replayer

Se você quiser reproduzir os arquivos Avro arquivados no Cloud Storage, precisará de outra Função, chamada replayer, que é acionada pelo HTTP. A Função do Cloud replayer pega os arquivos do período que você quer que sejam reproduzidos, expande e publica-os em ordem, em um novo fluxo do Cloud Pub/Sub.

Essa Função é acionada com uma solicitação HTTP POST, que fornece o período que você quer reproduzir. Ela responde com o nome do fluxo do Cloud Pub/Sub depois de concluída, ou com um código de erro e uma descrição, se não for possível carregar todos os dados arquivados no novo fluxo.

Se os arquivos ficarem muito grandes para serem reproduzidos de forma consistente, o aplicativo talvez precise de algo mais sofisticado. É possível dividir seus arquivos em seções menores ou fazer uma seleção de linguagem diferente para a Função replayer.

Outra opção é usar um job do Cloud Dataflow para dividi-lo em tarefas menores e executá-las em paralelo. A implementação desse sistema está fora do escopo deste documento, mas há ótimos exemplos no repositório do GitHub do GCP e na documentação do Cloud Dataflow.

Como criar o aplicativo materializador

Com esses eventos no livro-razão, cada um representando uma única transação, é possível integrar diferentes tipos de serviços ao sistema sem precisar escrever mais código nem coordenar diferentes equipes de aplicativos.

Por exemplo, este aplicativo ouve todas as mensagens em um determinado tópico, filtra por nome de um cliente e cria uma visualização materializada dos dados relevantes dele. O ideal é que você use-o quando precisar fazer consultas com latência muito baixa a informações sobre um usuário.

Analise os dados com antecedência, coloque-os em um armazenamento rápido e retorne uma resposta detalhada para melhorar o desempenho das consultas executadas com frequência.

Este aplicativo materializador pode ser composto por uma Função do Cloud. Essa função é acionada por um tópico do Cloud Pub/Sub, filtra as informações por ID do cliente e, se o ID do cliente corresponder ao que interessa, ela grava os dados relevantes no Cloud Memorystore.

Conclusão

A criação de uma arquitetura de sistema alimentado por eventos pode criar novas funcionalidades e adicionar flexibilidade a qualquer sistema que precise reagir ou entender o relacionamento entre os eventos. Quando a ordem determinística ou a capacidade de alta ingestão são importantes, usar o Cloud Spanner como um sistema de ingestão de eventos e o Cloud Pub/Sub como livro-razão de eventos pode formar uma base sólida e confiável para sua arquitetura de eventos. Depois de configurar uma base alimentada por eventos, é possível descobrir as muitas maneiras de simplificar os problemas que antes eram complicados de resolver com soluções do Cloud Functions ou do Cloud Pub/Sub.

A seguir

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

Enviar comentários sobre…