Como os microsserviços Netflix lidam com dados Pub-Sub

A tradução do artigo foi preparada especialmente para os alunos do curso “High Load Architect” .





1. Introdução


Na arquitetura de microsserviço da Netflix, a transferência de conjuntos de dados de um para vários pontos de extremidade pode ser extremamente difícil. Esses conjuntos de dados podem conter qualquer coisa, desde uma configuração de serviço até resultados de processamento em lote. Para otimizar o acesso, geralmente é necessário um banco de dados residente e as alterações devem ser enviadas imediatamente após a atualização dos dados.

Um exemplo que reflete a necessidade de distribuição distribuída de um conjunto de dados é mais ou menos assim: a qualquer momento, a Netflix realiza um grande número de testes A / B. Esses testes abrangem vários serviços e comandos, e os operadores de teste devem poder reconfigurar em tempo real. Também requer a capacidade de detectar nós que não puderam obter a configuração de teste mais recente e a capacidade de reverter para versões mais antigas, caso algo dê errado.

Outro exemplo de um conjunto de dados que precisa ser distribuído é a sequência de saída de um modelo de aprendizado de máquina: os resultados de seu trabalho podem ser usados ​​por várias equipes; no entanto, as equipes de ML não estão necessariamente interessadas em oferecer suporte a serviços de acesso ininterrupto em uma situação crítica. Em vez da situação em que cada equipe precisa criar backups para poder fazer uma reversão sucinta, é dada atenção especial para garantir que várias equipes possam usar os resultados de uma única equipe.

Sem suporte no nível da infraestrutura, cada equipe tenta implementar sua própria solução, mas acontece com equipes diferentes com sucesso variável. Os conjuntos de dados são de tamanhos diferentes, de alguns bytes a alguns gigabytes. É importante criar a capacidade de monitorar o desempenho dos processos e detectar defeitos usando ferramentas especiais, para que os operadores possam fazer alterações rapidamente sem precisar criar sua própria solução.


Divulgação de dados

Na Netflix, usamos um sistema de publicação / sub-dados interno chamado Gutenberg. O Gutenberg permite distribuir conjuntos de dados com controle de versão - os destinatários assinam os dados e recebem as versões mais recentes quando publicados. Cada versão do conjunto de dados permanece inalterada e contém uma representação completa dos dados, ou seja, não há dependência das versões anteriores. O Gutenberg permite visualizar versões antigas de dados, caso você precise, por exemplo, de depuração, resolução rápida de um problema de dados ou reciclagem de um modelo de aprendizado de máquina. Neste artigo, falaremos sobre a arquitetura de alto nível de Gutenberg.

Modelo de dados



1 tópico -> muitas versões

O design de alto nível de Gutenberg é o tema. O publicador publica dados dentro do tópico e os destinatários os extraem. A publicação no tópico é adicionada como uma versão separada. Esses são caracterizados por uma política de armazenamento específica que determina o número de versões, dependendo do caso de uso. Por exemplo, você pode configurar um tema para armazenar 10 versões ou versões dos últimos 10 dias.

Cada versão contém metadados (chaves e valores) e um ponteiro de dados. O ponteiro de dados pode ser considerado como metadado especial, indicando onde os dados publicados são realmente armazenados. Hoje, o Gutenberg suporta ponteiros de dados diretos (se a carga útil estiver gravada no valor do ponteiro de dados) e ponteiros de dados S3 (se a carga útil estiver armazenada no S3). Os ponteiros de dados diretos geralmente são usados ​​quando os dados são pequenos (menos de 1 MB) e o S3 é usado como armazenamento de backup, caso o volume de dados seja grande.


1 tópico -> muitos conjuntos publicados

Gutenberg oferece a capacidade de enviar uma publicação a um conjunto específico de usuários destinatários - por exemplo, um conjunto pode ser agrupado por uma região, aplicativo ou cluster específico. Isso pode ser usado para controlar a qualidade das alterações de dados ou para limitar o conjunto de dados para que um subconjunto de aplicativos possa se inscrever nele. Os editores determinam a área de publicação de uma versão específica dos dados e podem adicionar áreas aos dados publicados anteriormente. Observe que isso significa que o conceito da versão mais recente dos dados depende de uma área específica - os dois aplicativos podem receber as versões diferentes mais recentes dos dados, dependendo da área de publicação definida pelo editor. O serviço Gutenberg mapeia os aplicativos destinatários para as áreas de publicação antes de decidir o que enviar como a versão mais recente.

Casos de uso


O caso de uso mais comum do Gutenberg é distribuir dados de tamanhos diferentes de um editor para vários destinatários. Freqüentemente, os dados são armazenados na memória do destinatário e usados ​​como um "cache compartilhado", onde permanecem sempre disponíveis durante a execução do código do destinatário e são substituídos atomicamente sob o capô, se necessário. Muitos desses casos de uso podem ser agrupados em "configurações", como a configuração de cache do Open Connect Appliance , IDs de tipo de dispositivo suportados, metadados de métodos de pagamento suportados e configurações de teste A / B. Gutenberg fornece uma abstração entre publicar e receber esses dados, permitindo que os editores iterem livremente através de seus aplicativos sem afetar os destinatários posteriores. Em alguns casos, a publicação é feita usando uma interface de usuário gerenciada por Gutenberg, e as equipes não precisam tocar em seu próprio aplicativo de publicação.

Outro uso do sistema Gutenberg é um repositório de dados com versão. Isso é útil para aplicativos de aprendizado de máquina em que as equipes constroem e treinam modelos com base em dados históricos, veem como eles mudam ao longo do tempo, alteram determinados parâmetros e executam o aplicativo novamente. Geralmente, nos cálculos em lotes, o Gutenberg é usado para armazenar e distribuir os resultados desses cálculos como versões diferentes dos conjuntos de dados. Os casos de uso online assinam tópicos para fornecer dados em tempo real dos conjuntos de versões mais recentes, enquanto sistemas autônomos podem usar dados históricos dos mesmos tópicos, por exemplo, para ensinar um modelo de aprendizado de máquina.

É importante observar que o Gutenberg não foi projetado como um sistema de eventos, destina-se apenas ao controle de versão e distribuição de dados. Em particular, publicações frequentes não significam que o assinante seja obrigado a receber cada versão. Quando ele solicita uma atualização, ele recebe a versão mais recente, mesmo que no momento sua versão atual esteja muito atrás da atual. Os sistemas tradicionais pub-sub ou event são mais adequados para pequenas mensagens enviadas seqüencialmente. Ou seja, os destinatários podem criar uma idéia de todo o conjunto de dados consumindo todo o fluxo (compactado) de eventos. No entanto, Gutenberg pretende publicar e usar uma representação completa e imutável de um conjunto de dados.

Desenvolvimento e arquitetura


Gutenberg consiste em um serviço gRPC e uma API REST, além de uma biblioteca cliente Java que usa a API gRPC.


Arquitetura de alto nível

Cliente


A biblioteca do cliente Gutenberg lida com tarefas como gerenciar uma assinatura, carregar / descarregar S3, métricas Atlas e parâmetros que podem ser configurados usando as propriedades do Archaius . Ela interage com o serviço Gutenberg através do gRPC, usando Eureka para descobrir serviços.

Postagem


Os editores geralmente usam APIs de alto nível para publicar sequências, arquivos e matrizes de bytes. Dependendo do tamanho dos dados, eles podem ser publicados como um ponteiro direto para os dados ou enviados para o S3 e, em seguida, publicados como um ponteiro de dados S3. O cliente pode fazer upload da carga útil para o S3 em nome do editor ou publicar apenas os metadados da carga útil que já estão no S3.

Os ponteiros de dados diretos são automaticamente replicados globalmente. Os dados publicados no S3 são, por padrão, carregados pelo editor em várias áreas, embora também possam ser personalizados.

Gerenciamento de Assinaturas


A biblioteca do cliente fornece gerenciamento de assinaturas de destinatários. Isso permite que os usuários criem assinaturas para determinados tópicos dos quais a biblioteca extrai dados (por exemplo, do S3) para transferi-los para o destinatário definido pelo usuário. As assinaturas funcionam de acordo com o modelo de pesquisa - elas solicitam uma nova atualização do serviço a cada 30 segundos, enviando a versão que receberam por último. Os clientes assinados não usarão uma versão mais antiga dos dados do que a que eles têm atualmente se não forem corrigidos (consulte "tolerância a falhas" abaixo). A lógica de solicitação repetida é cabeada e configurável. Por exemplo, os usuários podem configurar o Gutenberg para usar versões mais antigas dos dados se o processo de download for interrompido ou para processar a versão mais recente dos dados na inicialização, na maioria das vezes, para trabalhar com alterações de dados incompatíveis com o feedback. O Gutenberg também fornece uma assinatura pré-configurada que armazena os dados mais recentes e os atualiza automaticamente quando as alterações chegam. Isso atende à maioria dos casos de uso de assinaturas, em que os assinantes se preocupam apenas com o valor atual a qualquer momento, o que permite aos usuários especificar um valor padrão, por exemplo, para um tópico que nunca foi publicado antes (por exemplo, se o tema for usado para configuração) ou se houver um erro dependendo do tópico (para evitar o bloqueio do lançamento do serviço quando houver um valor padrão válido).

API do destinatário


A Gutenberg também fornece APIs de cliente de alto nível, que possuem APIs de gRPC de baixo nível e fornecem funcionalidade adicional e transparência na execução da consulta. Um exemplo é o download de dados para um tema e versão específicos, amplamente utilizados por componentes conectados ao Netflix Hollow . Outro exemplo é o recebimento da versão mais recente de um tópico em um determinado momento - um caso de uso comum para depuração ou ensino de modelos de aprendizado de máquina.

Sustentabilidade e "transparência" do cliente


O Gutenberg foi projetado com o objetivo de permitir que os serviços de destinatário iniciem com êxito, em vez de garantir que eles comecem com os dados mais atuais. Por esse motivo, a biblioteca cliente foi construída com lógica de backup para lidar com estados quando não pode interagir com o serviço Gutenberg. Se as solicitações HTTP falharem, o cliente carregará o cache de metadados de backup do tópico publicado do S3 e trabalhará com ele. Esse cache contém todas as informações para decidir se deve aplicar a atualização e onde recuperar os dados (dos próprios metadados da publicação ou do S3). Isso permite que os clientes recuperem dados (que estão potencialmente desatualizados, dependendo do estado atual do cache de backup) sem usar o serviço.

Uma das vantagens de fornecer uma biblioteca cliente é a capacidade de obter métricas que podem ser usadas para relatar problemas de infraestrutura em geral e erros em aplicativos específicos. Hoje, essas métricas são usadas pela equipe de Gutenberg para monitorar nossa distribuição de publicações e alertas SLI em caso de problemas típicos. Alguns clientes também usam essas métricas para relatar erros específicos de aplicativos específicos, por exemplo, falhas de publicação individuais ou uma recusa de tópico específica.

Servidor


Gutenberg é um aplicativo Governator / Tomcat que fornece pontos de extremidade gRPC e REST. Ele usa o cluster Cassandra replicado globalmente para armazenar e distribuir metadados de publicação em cada região. As instâncias que processam solicitações de destinatário são dimensionadas separadamente das instâncias que processam solicitações de publicação. Há aproximadamente 1.000 vezes mais solicitações de publicação do que solicitações de publicação. Além disso, isso permite remover a dependência do fato da publicação no recebimento, para que um aumento repentino nas publicações não afete o recebimento e vice-versa.

Cada instância no Cluster de Solicitações de Destinatário processa seu próprio cache de memória das publicações recentes, retirando-o do Cassandra a cada poucos segundos. Isso é necessário para processar um grande número de solicitações de recebimento provenientes de clientes assinados sem transferir tráfego para o cluster Cassandra. Além disso, os caches com um pequeno pool de solicitações ttl protegem contra picos de consulta que podem retardar tanto o Cassandra que afetam toda a região. Tivemos situações em que erros repentinos coincidentes com a redistribuição de grandes grupos causaram interrupções no serviço Gutenberg. Além disso, usamos o limitador de simultaneidade adaptável encontrado no aplicativo original para suprimir aplicativos com comportamento incorreto sem afetar outros.

Nos casos em que os dados são publicados no S3 em várias regiões, o servidor decide qual segmento enviar de volta ao cliente para download, dependendo da localização do cliente. Ele também permite que o serviço forneça ao cliente um segmento na região "mais próxima" ou force o cliente a mudar para outra região se a região atual for desconectada por um motivo ou outro.

Antes de devolver os dados da assinatura aos destinatários, o Gutenberg primeiro verifica a consistência dos dados. Se a verificação falhar e o assinante já tiver recebido alguns dados, o serviço não retornará nada, o que realmente significa que a atualização não está disponível. Se o cliente assinante ainda não recebeu nenhum dado (geralmente isso significa que acabou de iniciar), o serviço solicita o histórico do tópico e retorna o último valor que passa na verificação de consistência. Isso ocorre pelo fato de observarmos atrasos episódicos na replicação no nível Cassandra, onde, quando os assinantes solicitam novos dados, os metadados associados à versão publicada mais recente eram apenas parcialmente replicados. Isso pode fazer com que o cliente receba dados incompletos, o que levará a erros na solicitação de dados ou na lógica de negócios. A realização de tais verificações de consistência no servidor protege os destinatários de alertas de possível consistência que acompanham a escolha de um serviço de armazém de dados.

A capacidade de monitorar publicações de tópicos e sites que usam esses tópicos é uma função importante para auditar e coletar informações de uso. Para coletar esses dados, o serviço intercepta solicitações de editores e destinatários (solicitações de atualização de dados de assinantes e outros) e as indexa no Elasticsearch usando o pipeline de dados Keystone . Portanto, temos a oportunidade de obter dados para monitorar tópicos usados ​​e que não estão mais lá. Publicamos links detalhados para o painel do Kibana a partir da interface do usuário interna, para que os proprietários do tema possam gerenciar seus assinantes de maneira independente.

Além de clusters que lidam com solicitações de editores e destinatários, o serviço Gutenberg lança outro cluster que processa solicitações periódicas. Em particular, ele resolve dois problemas:

  1. A cada poucos minutos, todas as publicações e metadados mais recentes são coletados e enviados para o S3. Isso inicia o início do cache de backup, usado pelo cliente, conforme descrito acima.
  2. O coletor de lixo exclui versões de tópicos que não atendem às políticas de retenção. Ele também exclui os dados associados a ele (por exemplo, objetos S3) e ajuda a garantir um ciclo de vida dos dados bem definido.

Tolerância a falhas


Snap


As implantações malsucedidas acontecem no mundo do desenvolvimento de aplicativos, e as reversões para versões anteriores são uma estratégia comum para corrigir esses problemas. A arquitetura orientada a dados complica a situação, porque o comportamento é determinado por dados que mudam com o tempo.

Os dados distribuídos por Gutenberg influenciam e, em muitos casos, controlam o comportamento do sistema. Isso significa que, se algo der errado, você precisará de uma maneira de reverter para uma boa versão comprovada dos dados. Para aliviar a situação, Gutenberg torna possível "vincular" o tema a uma versão específica. Os pinos substituem a versão mais recente dos dados e forçam o cliente a atualizar para esta versão, o que permite corrigir rapidamente uma situação crítica, em vez de tentar descobrir como publicar a versão de trabalho mais recente. Você pode até aplicar a ligação à área de publicação para que apenas os destinatários dessa área possam usar os dados. Os pinos também substituem os dados publicados durante a ligação ativa, mas quando o pino é excluído, os clientes receberão a versão mais recente, que pode ser a última versão fixada ou uma nova versão publicada enquanto a antiga foi fixada.

Implantação sequencial


Ao implantar um novo código, é recomendável criar novos assemblies com um subconjunto de tráfego, implantá-los gradualmente ou, de alguma outra maneira, reduzir os riscos de implantação, diminuindo a velocidade. , , .

, Gutenberg, — Spinnaker . , . , . , , , , , . , AWS- .


Gutenberg Netflix . Gutenberg , 6 . – , 1-2 , 12 .

24- , , . , 200, 7. - , , Hollow. , , , – 60, – 4.


, Gutenberg:

  • : Gutenberg Java-, Node.JS Python-. , REST API Gutenberg . , Node.JS Python.
  • : Gutenberg . Gutenberg.
  • : , . , .
  • : , Gutenberg, Gutenberg . , .
  • : , , . , Elasticsearch.
  • : Netflix – . , Gutenberg , .

Só isso. .

Source: https://habr.com/ru/post/pt479572/


All Articles