BLOG | NGINX

Gerenciamento de dados orientado a eventos para microsserviços

NGINX-Parte-de-F5-horiz-preto-tipo-RGB
Miniatura de Chris Richardson
Chris Richardson
Publicado em 04 de dezembro de 2015

Editor – Esta série de artigos em sete partes está agora completa:

  1. Introdução aos Microsserviços
  2. Construindo Microsserviços: Usando um gateway de API
  3. Construindo Microsserviços: Comunicação entre processos em uma arquitetura de microsserviços
  4. Descoberta de serviços em uma arquitetura de microsserviços
  5. Gerenciamento de dados orientado a eventos para microsserviços (este artigo)
  6. Escolhendo uma estratégia de implantação de microsserviços
  7. Refatorando um monólito em microsserviços

Você também pode baixar o conjunto completo de artigos, além de informações sobre a implementação de microsserviços usando o NGINX Plus, como um e-book – Microsserviços: Do design à implantação . E veja nossa série sobre a Arquitetura de Referência de Microsserviços e a página Soluções de Microsserviços .

Este é o quinto artigo de uma série sobre criação de aplicativos com microsserviços. O primeiro artigo apresenta o padrão de arquitetura de microsserviços e discute os benefícios e desvantagens do uso de microsserviços. O segundo<.htmla> e o terceiro artigos da série descrevem diferentes aspectos da comunicação dentro de uma arquitetura de microsserviços. O quarto artigo explora o problema intimamente relacionado da descoberta de serviços. Neste artigo, mudamos de assunto e analisamos os problemas de gerenciamento de dados distribuídos que surgem em uma arquitetura de microsserviços.

Microsserviços e o problema do gerenciamento de dados distribuídos

Um aplicativo monolítico normalmente tem um único banco de dados relacional. Um dos principais benefícios de usar um banco de dados relacional é que seu aplicativo pode usar transações ACID , que fornecem algumas garantias importantes:

  • Atomicidade – As mudanças são feitas atomicamente
  • Consistência – O estado do banco de dados é sempre consistente
  • Isolamento – Mesmo que as transações sejam executadas simultaneamente, parece que elas são executadas em série
  • Durabilidade – Uma vez que uma transação é confirmada, ela não pode ser desfeita

Como resultado, seu aplicativo pode simplesmente iniciar uma transação, alterar (inserir, atualizar e excluir) várias linhas e confirmar a transação.

Outro grande benefício de usar um banco de dados relacional é que ele fornece SQL, que é uma linguagem de consulta rica, declarativa e padronizada. Você pode facilmente escrever uma consulta que combine dados de várias tabelas. O planejador de consultas do RDBMS então determina a maneira mais ideal de executar a consulta. Você não precisa se preocupar com detalhes de baixo nível, como acessar o banco de dados. E, como todos os dados do seu aplicativo estão em um banco de dados, é fácil consultá-los.

Infelizmente, o acesso aos dados se torna muito mais complexo quando migramos para uma arquitetura de microsserviços. Isso ocorre porque os dados de propriedade de cada microsserviço são privados para esse microsserviço e só podem ser acessados por meio de sua API. Encapsular os dados garante que os microsserviços sejam fracamente acoplados e possam evoluir independentemente uns dos outros. Se vários serviços acessarem os mesmos dados, as atualizações de esquema exigirão atualizações coordenadas e demoradas para todos os serviços.

Para piorar a situação, diferentes microsserviços geralmente usam diferentes tipos de bancos de dados. Os aplicativos modernos armazenam e processam diversos tipos de dados e um banco de dados relacional nem sempre é a melhor escolha. Para alguns casos de uso, um banco de dados NoSQL específico pode ter um modelo de dados mais conveniente e oferecer desempenho e escalabilidade muito melhores. Por exemplo, faz sentido que um serviço que armazena e consulta texto use um mecanismo de busca de texto como o Elasticsearch. Da mesma forma, um serviço que armazena dados de gráficos sociais provavelmente deve usar um banco de dados de gráficos, como o Neo4j. Consequentemente, os aplicativos baseados em microsserviços geralmente usam uma mistura de bancos de dados SQL e NoSQL, a chamada abordagem de persistência poliglota .

Uma arquitetura particionada e poliglota-persistente para armazenamento de dados tem muitos benefícios, incluindo serviços fracamente acoplados e melhor desempenho e escalabilidade. No entanto, ele introduz alguns desafios de gerenciamento de dados distribuídos.

O primeiro desafio é como implementar transações comerciais que mantenham a consistência em vários serviços. Para entender por que isso é um problema, vamos dar uma olhada em um exemplo de uma loja B2B online. O Atendimento ao Cliente mantém informações sobre os clientes, incluindo suas linhas de crédito. O Serviço de Pedidos gerencia os pedidos e deve verificar se um novo pedido não excede o limite de crédito do cliente. Na versão monolítica deste aplicativo, o Order Service pode simplesmente usar uma transação ACID para verificar o crédito disponível e criar o pedido.

Por outro lado, em uma arquitetura de microsserviços, as tabelas ORDER e CUSTOMER são privadas para seus respectivos serviços, conforme mostrado no diagrama a seguir.

Cada serviço em uma arquitetura de microsserviços mantém uma tabela de banco de dados privada

O Order Service não pode acessar a tabela CUSTOMER diretamente. Ele só pode usar a API fornecida pelo Atendimento ao Cliente. O Order Service poderia potencialmente usar transações distribuídas , também conhecidas como confirmação de duas fases (2PC). Entretanto, 2PC geralmente não é uma opção viável em aplicações modernas. O teorema CAP exige que você escolha entre disponibilidade e consistência no estilo ACID, e disponibilidade geralmente é a melhor escolha. Além disso, muitas tecnologias modernas, como a maioria dos bancos de dados NoSQL, não suportam 2PC. Manter a consistência de dados entre serviços e bancos de dados é essencial, então precisamos de outra solução.

O segundo desafio é como implementar consultas que recuperem dados de vários serviços. Por exemplo, vamos imaginar que o aplicativo precisa exibir um cliente e seus pedidos recentes. Se o Order Service fornecer uma API para recuperar os pedidos de um cliente, você poderá recuperar esses dados usando uma junção do lado do aplicativo. O aplicativo recupera o cliente do Atendimento ao Cliente e os pedidos do cliente do Serviço de Pedidos. Suponha, no entanto, que o Order Service suporta apenas a pesquisa de pedidos por sua chave primária (talvez ele use um banco de dados NoSQL que suporta apenas recuperações baseadas em chave primária). Nessa situação, não há uma maneira óbvia de recuperar os dados necessários.

Arquitetura orientada a eventos

Para muitas aplicações, a solução é usar uma arquitetura orientada a eventos . Nessa arquitetura, um microsserviço publica um evento quando algo importante acontece, como quando ele atualiza uma entidade comercial. Outros microsserviços assinam esses eventos. Quando um microsserviço recebe um evento, ele pode atualizar suas próprias entidades comerciais, o que pode levar à publicação de mais eventos.

Você pode usar eventos para implementar transações comerciais que abrangem vários serviços. Uma transação consiste em uma série de etapas. Cada etapa consiste em um microsserviço atualizando uma entidade comercial e publicando um evento que aciona a próxima etapa. A sequência de diagramas a seguir mostra como você pode usar uma abordagem orientada a eventos para verificar o crédito disponível ao criar um pedido. Os microsserviços trocam eventos por meio de um Message Broker.

  1. O Order Service cria um Order com status NEW e publica um evento Order Created.

    Na etapa 1 de uma verificação de crédito em uma arquitetura de microsserviços, o Order Service publica um evento 'Order Created'

  2. O Atendimento ao Cliente consome o evento Pedido Criado, reserva crédito para o pedido e publica um evento Crédito Reservado.

    Em uma arquitetura de microsserviços, a segunda etapa de uma verificação de crédito é o Atendimento ao Cliente gerar um evento 'Crédito Reservado'

  3. O Serviço de Pedidos consome o evento Crédito Reservado e altera o status do pedido para ABERTO.

    Em uma arquitetura de microsserviços, a terceira etapa de uma verificação de crédito é o Order Service definir o status do pedido como 'Aberto'

Um cenário mais complexo pode envolver etapas adicionais, como reservar estoque ao mesmo tempo em que o crédito do cliente é verificado.

Desde que (a) cada serviço atualize atomicamente o banco de dados e publique um evento – mais sobre isso depois – e (b) o Message Broker garanta que os eventos sejam entregues pelo menos uma vez, você pode implementar transações comerciais que abrangem vários serviços. É importante observar que essas não são transações ACID. Elas oferecem garantias muito mais fracas, como consistência eventual . Este modelo de transação foi chamado de modelo BASE .

Você também pode usar eventos para manter visualizações materializadas que unem previamente dados de propriedade de vários microsserviços. O serviço que mantém a exibição assina os eventos relevantes e atualiza a exibição. Por exemplo, o Serviço de Atualização de Exibição de Pedidos do Cliente, que mantém uma exibição de Pedidos do Cliente, assina os eventos publicados pelo Atendimento ao Cliente e pelo Serviço de Pedidos.

Em uma arquitetura de microsserviços, um serviço pode assinar notificações de eventos publicadas por outros serviços como gatilhos para ação

Quando o Customer Order View Updater Service recebe um evento de cliente ou pedido, ele atualiza o armazenamento de dados do Customer Order View. Você pode implementar a Visualização de Pedidos do Cliente usando um banco de dados de documentos como o MongoDB e armazenar um documento para cada Cliente. O Serviço de Consulta de Exibição de Pedidos do Cliente manipula solicitações de um cliente e pedidos recentes consultando o armazenamento de dados de Exibição de Pedidos do Cliente.

Uma arquitetura orientada a eventos tem vários benefícios e desvantagens. Ele permite a implementação de transações que abrangem vários serviços e fornecem consistência eventual. Outro benefício é que ele também permite que um aplicativo mantenha visualizações materializadas. Uma desvantagem é que o modelo de programação é mais complexo do que quando se usam transações ACID. Muitas vezes, você deve implementar transações compensatórias para se recuperar de falhas no nível do aplicativo; por exemplo, você deve cancelar um pedido se a verificação de crédito falhar. Além disso, os aplicativos devem lidar com dados inconsistentes. Isso ocorre porque as alterações feitas pelas transações em andamento são visíveis. O aplicativo também pode detectar inconsistências se ler uma visualização materializada que ainda não foi atualizada. Outra desvantagem é que os assinantes precisam detectar e ignorar eventos duplicados.

Atingindo a Atomicidade

Em uma arquitetura orientada a eventos, há também o problema de atualizar atomicamente o banco de dados e publicar um evento. Por exemplo, o Order Service deve inserir uma linha na tabela ORDER e publicar um evento Order Created. É essencial que essas duas operações sejam feitas atomicamente. Se o serviço falhar após atualizar o banco de dados, mas antes de publicar o evento, o sistema se tornará inconsistente. A maneira padrão de garantir a atomicidade é usar uma transação distribuída envolvendo o banco de dados e o Message Broker. Entretanto, pelas razões descritas acima, como o teorema CAP, é exatamente isso que não queremos fazer.

Publicando eventos usando transações locais

Uma maneira de atingir a atomicidade é que o aplicativo publique eventos usando um processo de várias etapas envolvendo apenas transações locais . O truque é ter uma tabela EVENT, que funciona como uma fila de mensagens, no banco de dados que armazena o estado das entidades comerciais. O aplicativo inicia uma transação de banco de dados (local), atualiza o estado das entidades comerciais, insere um evento na tabela EVENT e confirma a transação. Um thread ou processo de aplicativo separado consulta a tabela EVENT, publica os eventos no Message Broker e, em seguida, usa uma transação local para marcar os eventos como publicados. O diagrama a seguir mostra o design.

Em uma arquitetura de microsserviços, obtenha atomicidade usando apenas transações locais para publicar eventos

O Order Service insere uma linha na tabela ORDER e insere um evento Order Created na tabela EVENT. O thread ou processo do Event Publisher consulta a tabela EVENT em busca de eventos não publicados, publica os eventos e, em seguida, atualiza a tabela EVENT para marcar os eventos como publicados.

Essa abordagem tem vários benefícios e desvantagens. Um benefício é que ele garante que um evento seja publicado para cada atualização sem depender do 2PC. Além disso, o aplicativo publica eventos de nível empresarial, o que elimina a necessidade de inferi-los. Uma desvantagem dessa abordagem é que ela é potencialmente propensa a erros, pois o desenvolvedor deve se lembrar de publicar eventos. Uma limitação dessa abordagem é que ela é desafiadora de implementar ao usar alguns bancos de dados NoSQL devido às suas capacidades limitadas de transação e consulta.

Essa abordagem elimina a necessidade de 2PC, fazendo com que o aplicativo use transações locais para atualizar o estado e publicar eventos. Vejamos agora uma abordagem que atinge a atomicidade fazendo com que o aplicativo simplesmente atualize o estado.

Minerando um log de transações de banco de dados

Outra maneira de atingir a atomicidade sem 2PC é que os eventos sejam publicados por um thread ou processo que extrai o log de transações ou confirmações do banco de dados. O aplicativo atualiza o banco de dados, o que resulta em alterações registradas no log de transações do banco de dados. O thread ou processo do Transaction Log Miner lê o log de transações e publica eventos no Message Broker. O diagrama a seguir mostra o design.

Em uma arquitetura de microsserviços, obtenha atomicidade minerando o log de transações em busca de eventos

Um exemplo dessa abordagem é o projeto de código aberto LinkedIn Databus . O Databus extrai o log de transações do Oracle e publica eventos correspondentes às alterações. O LinkedIn usa o Databus para manter vários armazenamentos de dados derivados consistentes com o sistema de registro.

Outro exemplo é o mecanismo de fluxos no AWS DynamoDB , que é um banco de dados NoSQL gerenciado. Um fluxo do DynamoDB contém a sequência de alterações ordenadas por tempo (operações de criação, atualização e exclusão) feitas nos itens em uma tabela do DynamoDB nas últimas 24 horas. Um aplicativo pode ler essas alterações do fluxo e, por exemplo, publicá-las como eventos.

A mineração de logs de transações tem vários benefícios e desvantagens. Um benefício é que ele garante que um evento seja publicado para cada atualização sem usar 2PC. A mineração de log de transações também pode simplificar o aplicativo separando a publicação de eventos da lógica de negócios do aplicativo. Uma grande desvantagem é que o formato do log de transações é proprietário de cada banco de dados e pode até mudar entre as versões do banco de dados. Além disso, pode ser difícil fazer engenharia reversa dos eventos comerciais de alto nível a partir das atualizações de baixo nível registradas no log de transações.

A mineração de log de transações elimina a necessidade do 2PC, pois faz com que o aplicativo faça apenas uma coisa: atualizar o banco de dados. Vamos agora dar uma olhada em uma abordagem diferente que elimina as atualizações e depende apenas de eventos.

Usando Event Sourcing

O sourcing de eventos atinge a atomicidade sem 2PC usando uma abordagem radicalmente diferente e centrada em eventos para persistir entidades comerciais. Em vez de armazenar o estado atual de uma entidade, o aplicativo armazena uma sequência de eventos de mudança de estado. O aplicativo reconstrói o estado atual de uma entidade reproduzindo os eventos. Sempre que o estado de uma entidade comercial muda, um novo evento é anexado à lista de eventos. Como salvar um evento é uma operação única, ele é inerentemente atômico.

Para ver como funciona o fornecimento de eventos, considere a entidade Order como exemplo. Em uma abordagem tradicional, cada pedido é mapeado para uma linha em uma tabela ORDER e para linhas em, por exemplo, uma tabela ORDER_LINE_ITEM. Mas ao usar o fornecimento de eventos, o Order Service armazena um Order na forma de seus eventos de mudança de estado: Criado, aprovado, enviado, cancelado. Cada evento contém dados suficientes para reconstruir o estado da Ordem.

Em uma arquitetura de microsserviços, obtenha atomicidade com fornecimento de eventos

Os eventos persistem em um Event Store, que é um banco de dados de eventos. A loja possui uma API para adicionar e recuperar eventos de uma entidade. O Event Store também se comporta como o Message Broker nas arquiteturas que descrevemos anteriormente. Ele fornece uma API que permite que serviços assinem eventos. A Event Store entrega todos os eventos a todos os assinantes interessados. O Event Store é a espinha dorsal de uma arquitetura de microsserviços orientada a eventos.

A terceirização de eventos tem vários benefícios. Ele resolve um dos principais problemas na implementação de uma arquitetura orientada a eventos e torna possível publicar eventos de forma confiável sempre que o estado muda. Como resultado, ele resolve problemas de consistência de dados em uma arquitetura de microsserviços. Além disso, como ele persiste eventos em vez de objetos de domínio, ele evita principalmente o problema de incompatibilidade de impedância objeto-relacional . O fornecimento de eventos também fornece um log de auditoria 100% confiável das alterações feitas em uma entidade comercial e possibilita implementar consultas temporais que determinam o estado de uma entidade em qualquer momento. Outro grande benefício do sourcing de eventos é que sua lógica de negócios consiste em entidades comerciais fracamente acopladas que trocam eventos. Isso torna muito mais fácil migrar de um aplicativo monolítico para uma arquitetura de microsserviços.

A terceirização de eventos também tem algumas desvantagens. É um estilo de programação diferente e desconhecido, por isso há uma curva de aprendizado. O armazenamento de eventos só oferece suporte direto à pesquisa de entidades comerciais por chave primária. Você deve usar o Command Query Responsibility Segregation (CQRS) para implementar consultas. Como resultado, os aplicativos devem manipular dados eventualmente consistentes.

Resumo

Em uma arquitetura de microsserviços, cada microsserviço tem seu próprio armazenamento de dados privado. Diferentes microsserviços podem usar diferentes bancos de dados SQL e NoSQL. Embora essa arquitetura de banco de dados tenha benefícios significativos, ela cria alguns desafios de gerenciamento de dados distribuídos. O primeiro desafio é como implementar transações comerciais que mantenham a consistência em vários serviços. O segundo desafio é como implementar consultas que recuperem dados de vários serviços.

Para muitas aplicações, a solução é usar uma arquitetura orientada a eventos. Um desafio na implementação de uma arquitetura orientada a eventos é como atualizar o estado atomicamente e como publicar eventos. Existem algumas maneiras de fazer isso, incluindo usar o banco de dados como uma fila de mensagens, mineração de log de transações e fornecimento de eventos.

Em postagens futuras do blog, continuaremos a nos aprofundar em outros aspectos dos microsserviços.

Editor – Esta série de artigos em sete partes está agora completa:

  1. Introdução aos Microsserviços
  2. Construindo Microsserviços: Usando um gateway de API
  3. Construindo Microsserviços: Comunicação entre processos em uma arquitetura de microsserviços
  4. Descoberta de serviços em uma arquitetura de microsserviços
  5. Gerenciamento de dados orientado a eventos para microsserviços (este artigo)
  6. Escolhendo uma estratégia de implantação de microsserviços
  7. Refatorando um monólito em microsserviços

Você também pode baixar o conjunto completo de artigos, além de informações sobre a implementação de microsserviços usando o NGINX Plus, como um e-book – Microsserviços: Do design à implantação . E veja nossa série sobre a Arquitetura de Referência de Microsserviços e a página Soluções de Microsserviços .

O blogueiro convidado Chris Richardson é o fundador do CloudFoundry.com original, um dos primeiros Java PaaS (Plataforma como Serviço) para Amazon EC2. Agora, ele presta consultoria a organizações para melhorar a maneira como elas desenvolvem e implantam aplicativos. Ele também escreve regularmente sobre microsserviços em http://microservices.io .


"Esta postagem do blog pode fazer referência a produtos que não estão mais disponíveis e/ou não têm mais suporte. Para obter as informações mais atualizadas sobre os produtos e soluções F5 NGINX disponíveis, explore nossa família de produtos NGINX . O NGINX agora faz parte do F5. Todos os links anteriores do NGINX.com redirecionarão para conteúdo semelhante do NGINX no F5.com."