Não apenas processamento: como criamos um banco de dados distribuído a partir do Kafka Streams e o que resultou disso

Oi, Habr!

Lembramos que seguindo o livro sobre Kafka publicamos um trabalho igualmente interessante sobre a biblioteca API de fluxos do Kafka.

Não apenas processamento: como criamos um banco de dados distribuído a partir do Kafka Streams e o que resultou disso

Por enquanto, a comunidade está apenas aprendendo os limites desta ferramenta poderosa. Assim, foi publicado recentemente um artigo, cuja tradução gostaríamos de apresentar a vocês. Por experiência própria, o autor conta como transformar Kafka Streams em um armazenamento distribuído de dados. Gostar de ler!

Biblioteca Apache Streams Kafka usado mundialmente em empresas para processamento de fluxo distribuído no Apache Kafka. Um dos aspectos subestimados desta estrutura é que ela permite armazenar o estado local produzido com base no processamento de threads.

Neste artigo, contarei como nossa empresa conseguiu aproveitar essa oportunidade de maneira lucrativa ao desenvolver um produto para segurança de aplicativos em nuvem. Usando Kafka Streams, criamos microsserviços de estado compartilhado, cada um dos quais serve como uma fonte de informações confiáveis, tolerante a falhas e altamente disponível sobre o estado dos objetos no sistema. Para nós, este é um avanço tanto em termos de confiabilidade quanto de facilidade de suporte.

Se você está interessado em uma abordagem alternativa que permita utilizar um único banco de dados central para suportar o estado formal de seus objetos, leia-a, será interessante...

Por que pensamos que era hora de mudar a forma como trabalhamos com estado compartilhado

Precisávamos manter o estado de vários objetos com base nos relatórios dos agentes (por exemplo: o site estava sob ataque)? Antes de migrar para o Kafka Streams, muitas vezes dependíamos de um único banco de dados central (+ API de serviço) para gerenciamento de estado. Essa abordagem tem suas desvantagens: situações intensivas de encontros manter a consistência e a sincronização torna-se um verdadeiro desafio. O banco de dados pode se tornar um gargalo ou acabar em condição de corrida e sofrem de imprevisibilidade.

Não apenas processamento: como criamos um banco de dados distribuído a partir do Kafka Streams e o que resultou disso

Figura 1: Um cenário típico de estado dividido visto antes da transição para
Kafka e Kafka Streams: os agentes comunicam suas visualizações via API, o estado atualizado é calculado por meio de um banco de dados central

Conheça o Kafka Streams, facilitando a criação de microsserviços de estado compartilhado

Há cerca de um ano, decidimos analisar atentamente os nossos cenários estatais partilhados para resolver estas questões. Decidimos imediatamente experimentar o Kafka Streams - sabemos o quão escalonável, altamente disponível e tolerante a falhas ele é, que rica funcionalidade de streaming ele possui (transformações, incluindo as com estado). Exatamente o que precisávamos, sem mencionar o quão maduro e confiável o sistema de mensagens se tornou em Kafka.

Cada um dos microsserviços com estado que criamos foi construído sobre uma instância do Kafka Streams com uma topologia bastante simples. Consistia em 1) uma fonte 2) um processador com um armazenamento persistente de valores-chave 3) um coletor:

Não apenas processamento: como criamos um banco de dados distribuído a partir do Kafka Streams e o que resultou disso

Figura 2: A topologia padrão de nossas instâncias de streaming para microsserviços com estado. Observe que também há um repositório aqui que contém metadados de planejamento.

Nessa nova abordagem, os agentes redigem mensagens que são inseridas no tópico de origem e os consumidores — digamos, um serviço de notificação por email — recebem o estado compartilhado computado por meio do coletor (tópico de saída).

Não apenas processamento: como criamos um banco de dados distribuído a partir do Kafka Streams e o que resultou disso

Figura 3: Novo exemplo de fluxo de tarefas para um cenário com microsserviços compartilhados: 1) o agente gera uma mensagem que chega ao tópico de origem do Kafka; 2) um microsserviço com estado compartilhado (usando Kafka Streams) o processa e grava o estado calculado no tópico final do Kafka; após o que 3) os consumidores aceitam o novo estado

Ei, esse armazenamento de valores-chave integrado é realmente muito útil!

Conforme mencionado acima, nossa topologia de estado compartilhado contém um armazenamento de valores-chave. Encontramos diversas opções de utilização, e duas delas estão descritas a seguir.

Opção nº 1: use um armazenamento de valores-chave para cálculos

Nosso primeiro armazenamento de valores-chave continha os dados auxiliares necessários para os cálculos. Por exemplo, em alguns casos o estado partilhado foi determinado pelo princípio da “maioria de votos”. O repositório poderia conter todos os relatórios mais recentes do agente sobre o status de algum objeto. Então, quando recebêssemos um novo relatório de um agente ou de outro, poderíamos salvá-lo, recuperar relatórios de todos os outros agentes sobre o estado do mesmo objeto do armazenamento e repetir o cálculo.
A Figura 4 abaixo mostra como expusemos o armazenamento de chave/valor ao método de processamento do processador para que a nova mensagem pudesse então ser processada.

Não apenas processamento: como criamos um banco de dados distribuído a partir do Kafka Streams e o que resultou disso

Ilustração 4: Abrimos o acesso ao armazenamento de valores-chave para o método de processamento do processador (depois disso, todo script que trabalha com estado compartilhado deve implementar o método doProcess)

Opção nº 2: Criando uma API CRUD sobre Kafka Streams

Tendo estabelecido nosso fluxo de tarefas básico, começamos a tentar escrever uma API RESTful CRUD para nossos microsserviços de estado compartilhado. Queríamos ser capazes de recuperar o estado de alguns ou de todos os objetos, bem como definir ou remover o estado de um objeto (útil para suporte de back-end).

Para oferecer suporte a todas as APIs Get State, sempre que precisávamos recalcular o estado durante o processamento, nós o armazenávamos em um armazenamento de chave-valor integrado por um longo tempo. Neste caso, torna-se bastante simples implementar tal API utilizando uma única instância do Kafka Streams, conforme mostrado na listagem abaixo:

Não apenas processamento: como criamos um banco de dados distribuído a partir do Kafka Streams e o que resultou disso

Figura 5: Usando o armazenamento de valores-chave integrado para obter o estado pré-computado de um objeto

Atualizar o estado de um objeto por meio da API também é fácil de implementar. Basicamente, tudo que você precisa fazer é criar um produtor Kafka e usá-lo para fazer um registro que contenha o novo estado. Isto garante que todas as mensagens geradas através da API serão processadas da mesma forma que aquelas recebidas de outros produtores (por exemplo, agentes).

Não apenas processamento: como criamos um banco de dados distribuído a partir do Kafka Streams e o que resultou disso

Figura 6: Você pode definir o estado de um objeto usando o produtor Kafka

Pequena complicação: Kafka tem muitas partições

Em seguida, queríamos distribuir a carga de processamento e melhorar a disponibilidade, fornecendo um cluster de microsserviços de estado compartilhado por cenário. A configuração foi muito fácil: depois que configuramos todas as instâncias para serem executadas sob o mesmo ID de aplicativo (e os mesmos servidores de inicialização), quase todo o resto foi feito automaticamente. Também especificamos que cada tópico de origem consistiria em diversas partições, de modo que cada instância pudesse receber um subconjunto de tais partições.

Mencionarei também que é prática comum fazer uma cópia de backup do armazenamento de estado para que, por exemplo, em caso de recuperação após uma falha, transfira essa cópia para outra instância. Para cada armazenamento de estado no Kafka Streams, um tópico replicado é criado com um log de alterações (que rastreia atualizações locais). Assim, Kafka apoia constantemente a reserva estatal. Portanto, no caso de falha de uma ou outra instância do Kafka Streams, o armazenamento de estado pode ser rapidamente restaurado em outra instância, para onde irão as partições correspondentes. Nossos testes mostraram que isso é feito em questão de segundos, mesmo que existam milhões de registros na loja.

Passando de um único microsserviço com estado compartilhado para um cluster de microsserviços, torna-se menos trivial implementar a API Get State. Na nova situação, o armazenamento de estado de cada microsserviço contém apenas parte do quadro geral (aqueles objetos cujas chaves foram mapeadas para uma partição específica). Tivemos que determinar qual instância continha o estado do objeto que precisávamos e fizemos isso com base nos metadados do thread, conforme mostrado abaixo:

Não apenas processamento: como criamos um banco de dados distribuído a partir do Kafka Streams e o que resultou disso

Figura 7: Usando metadados de fluxo, determinamos de qual instância consultar o estado do objeto desejado; uma abordagem semelhante foi usada com a API GET ALL

Principais conclusões

Os armazenamentos estaduais em Kafka Streams podem servir como um banco de dados distribuído de fato,

  • constantemente replicado em Kafka
  • Uma API CRUD pode ser facilmente construída sobre esse sistema
  • Lidar com múltiplas partições é um pouco mais complicado
  • Também é possível adicionar um ou mais armazenamentos de estado à topologia de streaming para armazenar dados auxiliares. Esta opção pode ser usada para:
  • Armazenamento de dados de longo prazo necessário para cálculos durante o processamento de fluxo
  • Armazenamento de dados de longo prazo que pode ser útil na próxima vez que a instância de streaming for provisionada
  • muito mais...

Essas e outras vantagens tornam o Kafka Streams adequado para manter o estado global em um sistema distribuído como o nosso. O Kafka Streams provou ser muito confiável na produção (praticamente não tivemos perda de mensagens desde a implantação) e estamos confiantes de que seus recursos não pararão por aí!

Fonte: habr.com

Adicionar um comentário