Comunicação serviço-a-serviço
Gorjeta
Este conteúdo é um excerto do eBook, Architecting Cloud Native .NET Applications for Azure, disponível no .NET Docs ou como um PDF transferível gratuito que pode ser lido offline.
Passando do cliente front-end, agora abordamos os microsserviços back-end que se comunicam entre si.
Ao criar um aplicativo nativo da nuvem, você desejará ser sensível à forma como os serviços de back-end se comunicam entre si. Idealmente, quanto menos comunicação inter-serviços, melhor. No entanto, evitar nem sempre é possível, pois os serviços de back-end geralmente dependem uns dos outros para concluir uma operação.
Existem várias abordagens amplamente aceites para implementar a comunicação entre serviços. O tipo de interação de comunicação muitas vezes determinará a melhor abordagem.
Considere os seguintes tipos de interação:
Consulta – quando um microsserviço de chamada requer uma resposta de um microsserviço chamado, como "Ei, dê-me as informações do comprador para um determinado ID de cliente".
Comando – quando o microsserviço de chamada precisa de outro microsserviço para executar uma ação, mas não requer uma resposta, como "Ei, basta enviar esta ordem".
Evento – quando um microsserviço, chamado editor, gera um evento cujo estado foi alterado ou uma ação ocorreu. Outros microsserviços, chamados de assinantes, que estão interessados, podem reagir ao evento adequadamente. O editor e os assinantes não estão cientes um do outro.
Os sistemas de microsserviços normalmente usam uma combinação desses tipos de interação ao executar operações que exigem interação entre serviços. Vamos dar uma olhada em cada um deles e como você pode implementá-los.
Consultas
Muitas vezes, um microsserviço pode precisar consultar outro, exigindo uma resposta imediata para concluir uma operação. Um microsserviço de carrinho de compras pode precisar de informações sobre o produto e um preço para adicionar um item ao seu carrinho de compras. Há muitas abordagens para implementar operações de consulta.
Mensagens de solicitação/resposta
Uma opção para implementar esse cenário é que o microsserviço back-end de chamada faça solicitações HTTP diretas para os microsserviços que precisa consultar, mostrado na Figura 4-8.
Figura 4-8. Comunicação HTTP direta
Embora as chamadas HTTP diretas entre microsserviços sejam relativamente simples de implementar, deve-se tomar cuidado para minimizar essa prática. Para começar, essas chamadas são sempre síncronas e bloquearão a operação até que um resultado seja retornado ou a solicitação atinja o tempo limite. O que antes eram serviços autônomos e independentes, capazes de evoluir de forma independente e implantar com frequência, agora se tornam acoplados uns aos outros. À medida que o acoplamento entre microsserviços aumenta, seus benefícios arquitetônicos diminuem.
Executar uma solicitação pouco frequente que faz uma única chamada HTTP direta para outro microsserviço pode ser aceitável para alguns sistemas. No entanto, chamadas de alto volume que invocam chamadas HTTP diretas para vários microsserviços não são aconselháveis. Eles podem aumentar a latência e afetar negativamente o desempenho, a escalabilidade e a disponibilidade do seu sistema. Pior ainda, uma longa série de comunicação HTTP direta pode levar a cadeias profundas e complexas de chamadas de microsserviços síncronas, mostradas na Figura 4-9:
Figura 4-9. Encadeamento de consultas HTTP
Você certamente pode imaginar o risco no design mostrado na imagem anterior. O que acontece se o Passo #3 falhar? Ou o Passo #8 falha? Como você se recupera? E se a Etapa #6 estiver lenta porque o serviço subjacente está ocupado? Como continuar? Mesmo que tudo funcione corretamente, pense na latência que essa chamada incorreria, que é a soma da latência de cada etapa.
O grande grau de acoplamento na imagem anterior sugere que os serviços não foram modelados da melhor forma. Caberia à equipa rever o seu design.
Padrão de Vista Materializada
Uma opção popular para remover o acoplamento de microsserviços é o padrão Materialized View. Com esse padrão, um microsserviço armazena sua própria cópia local e desnormalizada de dados que pertencem a outros serviços. Em vez de o microsserviço Carrinho de Compras consultar os microsserviços Catálogo de Produtos e Preços, ele mantém sua própria cópia local desses dados. Esse padrão elimina acoplamentos desnecessários e melhora a confiabilidade e o tempo de resposta. Toda a operação é executada dentro de um único processo. Exploramos esse padrão e outras preocupações com dados no Capítulo 5.
Padrão do agregador de serviços
Outra opção para eliminar o acoplamento de microsserviço a microsserviço é um microsserviço agregador, mostrado em roxo na Figura 4-10.
Figura 4-10. Microsserviço agregador
O padrão isola uma operação que faz chamadas para vários microsserviços de back-end, centralizando sua lógica em um microsserviço especializado. O microsserviço agregador de checkout roxo na figura anterior orquestra o fluxo de trabalho para a operação Checkout. Ele inclui chamadas para vários microsserviços de back-end em uma ordem seqüenciada. Os dados do fluxo de trabalho são agregados e devolvidos ao chamador. Embora ainda implemente chamadas HTTP diretas, o microsserviço agregador reduz as dependências diretas entre microsserviços back-end.
Padrão de solicitação/resposta
Outra abordagem para dissociar mensagens HTTP síncronas é um padrão de solicitação-resposta, que usa comunicação de enfileiramento. A comunicação através de uma fila é sempre um canal unidirecional, com um produtor a enviar a mensagem e o consumidor a recebê-la. Com esse padrão, uma fila de solicitações e uma fila de resposta são implementadas, mostradas na Figura 4-11.
Figura 4-11. Padrão de solicitação-resposta
Aqui, o produtor de mensagens cria uma mensagem baseada em consulta que contém uma ID de correlação exclusiva e a coloca em uma fila de solicitações. O serviço de consumo coloca as mensagens em fila, processa-as e coloca a resposta na fila de respostas com o mesmo ID de correlação. O serviço de produção descoloca a mensagem na fila, faz a correspondência com a ID de correlação e continua o processamento. Abordamos as filas em detalhes na próxima seção.
Comandos
Outro tipo de interação de comunicação é um comando. Um microsserviço pode precisar de outro microsserviço para executar uma ação. O microsserviço de Encomenda pode necessitar do microsserviço de Envio para criar um envio para uma encomenda aprovada. Na Figura 4-12, um microsserviço, chamado de Produtor, envia uma mensagem para outro microsserviço, o Consumidor, ordenando-lhe que faça algo.
Figura 4-12. Interação de comando com uma fila
Na maioria das vezes, o produtor não precisa de uma resposta e pode disparar e esquecer a mensagem. Se for necessária uma resposta, o Consumidor envia uma mensagem separada de volta ao Produtor em outro canal. Uma mensagem de comando é melhor enviada de forma assíncrona com uma fila de mensagens. suportado por um agente de mensagens leve. No diagrama anterior, observe como uma fila separa e separa ambos os serviços.
Como muitas filas de mensagens podem enviar a mesma mensagem mais de uma vez, conhecida como entrega pelo menos uma vez, o consumidor deve ser capaz de identificar e lidar com esses cenários corretamente usando os padrões de processamento de mensagens idempotentes relevantes.
Uma fila de mensagens é uma construção intermediária através da qual um produtor e um consumidor passam uma mensagem. As filas implementam um padrão de mensagens ponto a ponto assíncrono. O Produtor sabe para onde um comando precisa ser enviado e rotas apropriadas. A fila garante que uma mensagem seja processada exatamente por uma das instâncias do consumidor que estão lendo do canal. Nesse cenário, o serviço ao produtor ou ao consumidor pode ser dimensionado sem afetar o outro. Além disso, as tecnologias podem ser díspares de cada lado, o que significa que podemos ter um microsserviço Java chamando um microsserviço Golang .
No capítulo 1, falamos sobre serviços de apoio. Os serviços de suporte são recursos auxiliares dos quais os sistemas nativos da nuvem dependem. As filas de mensagens são serviços de backup. A nuvem do Azure dá suporte a dois tipos de filas de mensagens que seus sistemas nativos da nuvem podem consumir para implementar mensagens de comando: Filas de Armazenamento do Azure e Filas do Barramento de Serviço do Azure.
Filas de Armazenamento do Azure
As filas de armazenamento do Azure oferecem uma infraestrutura de fila simples que é rápida, acessível e apoiada por contas de armazenamento do Azure.
As Filas de Armazenamento do Azure apresentam um mecanismo de enfileiramento baseado em REST com mensagens confiáveis e persistentes. Eles fornecem um conjunto mínimo de recursos, mas são baratos e armazenam milhões de mensagens. Sua capacidade varia de até 500 TB. Uma única mensagem pode ter até 64 KB de tamanho.
Você pode acessar mensagens de qualquer lugar do mundo por meio de chamadas autenticadas usando HTTP ou HTTPS. As filas de armazenamento podem ser expandidas para um grande número de clientes simultâneos para lidar com picos de tráfego.
Dito isto, existem limitações com o serviço:
A ordem das mensagens não é garantida.
Uma mensagem só pode persistir durante sete dias antes de ser removida automaticamente.
O suporte para gerenciamento de estado, deteção de duplicados ou transações não está disponível.
A Figura 4-13 mostra a hierarquia de uma Fila de Armazenamento do Azure.
Figura 4-13. Hierarquia da fila de armazenamento
Na figura anterior, observe como as filas de armazenamento armazenam suas mensagens na conta de Armazenamento do Azure subjacente.
Para desenvolvedores, a Microsoft fornece várias bibliotecas do lado do cliente e do servidor para processamento de filas de armazenamento. A maioria das principais plataformas são suportadas, incluindo .NET, Java, JavaScript, Ruby, Python e Go. Os desenvolvedores nunca devem se comunicar diretamente com essas bibliotecas. Isso acoplará firmemente seu código de microsserviço ao serviço de Fila de Armazenamento do Azure. É uma prática melhor isolar os detalhes de implementação da API. Introduza uma camada de intermediação, ou API intermediária, que expõe operações genéricas e encapsula a biblioteca concreta. Esse acoplamento flexível permite que você troque um serviço de fila por outro sem ter que fazer alterações no código de serviço principal.
As filas de Armazenamento do Azure são uma opção econômica para implementar mensagens de comando em seus aplicativos nativos da nuvem. Especialmente quando um tamanho de fila excede 80 GB ou um conjunto de recursos simples é aceitável. Você paga apenas pelo armazenamento das mensagens; Não há taxas horárias fixas.
Filas do Azure Service Bus
Para requisitos de mensagens mais complexos, considere as filas do Barramento de Serviço do Azure.
Sentado no topo de uma infraestrutura de mensagens robusta, o Barramento de Serviço do Azure dá suporte a um modelo de mensagens intermediadas. As mensagens são armazenadas de forma confiável em um corretor (a fila) até serem recebidas pelo consumidor. A fila garante a entrega de mensagens First-In/First-Out (FIFO), respeitando a ordem em que as mensagens foram adicionadas à fila.
O tamanho de uma mensagem pode ser muito maior, até 256 KB. As mensagens persistem na fila por um período de tempo ilimitado. O Service Bus suporta não apenas chamadas baseadas em HTTP, mas também fornece suporte total para o protocolo AMQP. AMQP é um padrão aberto entre fornecedores que suporta um protocolo binário e graus mais altos de confiabilidade.
O Service Bus fornece um rico conjunto de recursos, incluindo suporte a transações e um recurso de deteção de duplicatas. A fila garante "no máximo uma entrega" por mensagem. Ele descarta automaticamente uma mensagem que já foi enviada. Se um produtor estiver em dúvida, ele pode reenviar a mesma mensagem, e o Service Bus garante que apenas uma cópia será processada. A deteção de duplicados liberta-o de ter de construir canalizações de infraestruturas adicionais.
Mais dois recursos corporativos são particionamento e sessões. Uma fila convencional do Service Bus é manipulada por um único agente de mensagens e armazenada em um único armazenamento de mensagens. Mas, o particionamento do Service Bus espalha a fila por vários agentes de mensagens e armazenamentos de mensagens. A taxa de transferência geral não é mais limitada pelo desempenho de um único agente de mensagens ou armazenamento de mensagens. Uma interrupção temporária de um armazenamento de mensagens não torna uma fila particionada indisponível.
As Sessões do Barramento de Serviço fornecem uma maneira de mensagens relacionadas ao grupo. Imagine um cenário de fluxo de trabalho em que as mensagens devem ser processadas em conjunto e a operação concluída no final. Para aproveitar, as sessões devem ser explicitamente habilitadas para a fila e cada mensagem relacionada deve conter a mesma ID de sessão.
No entanto, há algumas ressalvas importantes: o tamanho das filas do Barramento de Serviço é limitado a 80 GB, o que é muito menor do que o que está disponível nas filas da loja. Além disso, as filas do Barramento de Serviço incorrem em um custo base e cobrança por operação.
A Figura 4-14 descreve a arquitetura de alto nível de uma fila do Service Bus.
Figura 4-14. Fila do Service Bus
Na figura anterior, observe a relação ponto-a-ponto. Duas instâncias do mesmo provedor estão enfileirando mensagens em uma única fila do Service Bus. Cada mensagem é consumida por apenas uma das três instâncias de consumo à direita. Em seguida, discutimos como implementar mensagens onde diferentes consumidores podem estar interessados na mesma mensagem.
evento
O enfileiramento de mensagens é uma maneira eficaz de implementar a comunicação em que um produtor pode enviar uma mensagem de forma assíncrona a um consumidor. No entanto, o que acontece quando muitos consumidores diferentes estão interessados na mesma mensagem? Uma fila de mensagens dedicada para cada consumidor não seria bem dimensionada e se tornaria difícil de gerenciar.
Para resolver esse cenário, passamos para o terceiro tipo de interação de mensagem, o evento. Um microsserviço anuncia que uma ação ocorreu. Outros microsserviços, se interessados, reagem à ação ou evento. Isso também é conhecido como o estilo arquitetônico orientado a eventos.
O evento é um processo de duas etapas. Para uma determinada alteração de estado, um microsserviço publica um evento para um agente de mensagens, tornando-o disponível para qualquer outro microsserviço interessado. O microsserviço interessado é notificado inscrevendo-se no evento no agente de mensagens. Use o padrão Publicar/Assinar para implementar a comunicação baseada em eventos.
A Figura 4-15 mostra um microsserviço de carrinho de compras publicando um evento com dois outros microsserviços assinando-o.
Figura 4-15. Mensagens orientadas a eventos
Observe o componente de barramento de eventos que fica no meio do canal de comunicação. É uma classe personalizada que encapsula o agente de mensagens e o separa do aplicativo subjacente. Os microsserviços de encomenda e inventário operam o evento de forma independente, sem conhecimento mútuo, nem o microsserviço do cesto de compras. Quando o evento registrado é publicado no ônibus do evento, eles agem de acordo com ele.
Com os eventos, passamos da tecnologia de filas para os tópicos. Um tópico é semelhante a uma fila, mas suporta um padrão de mensagens um-para-muitos. Um microsserviço publica uma mensagem. Vários microsserviços de assinatura podem optar por receber e agir de acordo com essa mensagem. A Figura 4-16 mostra uma arquitetura de tópicos.
Figura 4-16. Arquitetura de tópicos
Na figura anterior, os editores enviam mensagens para o tópico. No final, os subscritores recebem mensagens das subscrições. No meio, o tópico encaminha mensagens para assinaturas com base em um conjunto de regras, mostrado em caixas azuis escuras. As regras funcionam como um filtro que encaminha mensagens específicas para uma assinatura. Aqui, um evento "GetPrice" seria enviado para o preço e registrando assinaturas como a assinatura de registro optou por receber todas as mensagens. Um evento "GetInformation" seria enviado para as assinaturas de informações e registro.
A nuvem do Azure dá suporte a dois serviços de tópicos diferentes: Tópicos do Barramento de Serviço do Azure e Azure EventGrid.
Tópicos do Azure Service Bus
Sentado sobre o mesmo modelo robusto de mensagem intermediada das filas do Barramento de Serviço do Azure estão os Tópicos do Barramento de Serviço do Azure. Um tópico pode receber mensagens de vários editores independentes e enviar mensagens para até 2.000 assinantes. As assinaturas podem ser adicionadas ou removidas dinamicamente em tempo de execução sem parar o sistema ou recriar o tópico.
Muitos recursos avançados das filas do Barramento de Serviço do Azure também estão disponíveis para tópicos, incluindo suporte a Deteção de Duplicados e Transações. Por padrão, os tópicos do Service Bus são manipulados por um único agente de mensagens e armazenados em um único armazenamento de mensagens. Mas, o particionamento do Service Bus dimensiona um tópico espalhando-o por muitos agentes de mensagens e armazenamentos de mensagens.
A Entrega de Mensagens Agendada marca uma mensagem com um horário específico para processamento. A mensagem não aparecerá no tópico antes desse período. O Adiamento de Mensagens permite adiar uma recuperação de uma mensagem para um momento posterior. Ambos são comumente usados em cenários de processamento de fluxo de trabalho onde as operações são processadas em uma ordem específica. Você pode adiar o processamento de mensagens recebidas até que o trabalho anterior tenha sido concluído.
Os tópicos do Service Bus são uma tecnologia robusta e comprovada para permitir a comunicação de publicação/assinatura em seus sistemas nativos da nuvem.
Grelha de Eventos do Azure
Enquanto o Barramento de Serviço do Azure é um agente de mensagens testado em batalha com um conjunto completo de recursos corporativos, a Grade de Eventos do Azure é a nova criança no bloco.
À primeira vista, a Grade de Eventos pode parecer apenas mais um sistema de mensagens baseado em tópicos. No entanto, é diferente em muitos aspetos. Focado em cargas de trabalho orientadas a eventos, ele permite o processamento de eventos em tempo real, a integração profunda do Azure e uma plataforma aberta - tudo em infraestrutura sem servidor. Ele foi projetado para aplicativos contemporâneos nativos da nuvem e sem servidor
Como um backplane de eventos centralizado, ou pipe, a Grade de Eventos reage a eventos dentro dos recursos do Azure e de seus próprios serviços.
As notificações de eventos são publicadas em um Tópico da Grade de Eventos, que, por sua vez, encaminha cada evento para uma assinatura. Os subscritores mapeiam as subscrições e consomem os eventos. Tal como o Service Bus, a Grelha de Eventos suporta um modelo de subscritor filtrado em que uma subscrição define regras para os eventos que pretende receber. A Grade de Eventos fornece uma taxa de transferência rápida com uma garantia de 10 milhões de eventos por segundo, permitindo a entrega quase em tempo real - muito mais do que o Barramento de Serviço do Azure pode gerar.
Um ponto ideal para a Grade de Eventos é sua profunda integração na estrutura da infraestrutura do Azure. Um recurso do Azure, como o Cosmos DB, pode publicar eventos internos diretamente em outros recursos do Azure interessados - sem a necessidade de código personalizado. A Grade de Eventos pode publicar eventos de uma Assinatura do Azure, Grupo de Recursos ou Serviço, dando aos desenvolvedores um controle refinado sobre o ciclo de vida dos recursos de nuvem. No entanto, a Grade de Eventos não está limitada ao Azure. É uma plataforma aberta que pode consumir eventos HTTP personalizados publicados a partir de aplicações ou serviços de terceiros e encaminhar eventos para subscritores externos.
Ao publicar e subscrever eventos nativos a partir de recursos do Azure, não é necessária codificação. Com uma configuração simples, você pode integrar eventos de um recurso do Azure para outro, aproveitando o encanamento interno para Tópicos e Assinaturas. A Figura 4-17 mostra a anatomia da Grade de Eventos.
Figura 4-17. Anatomia da grelha de eventos
Uma grande diferença entre o EventGrid e o Service Bus é o padrão subjacente de troca de mensagens.
O Service Bus implementa um modelo de pull de estilo mais antigo no qual o assinante downstream pesquisa ativamente a assinatura do tópico em busca de novas mensagens. Do lado positivo, esta abordagem dá ao assinante controlo total do ritmo a que processa as mensagens. Ele controla quando e quantas mensagens processar a qualquer momento. As mensagens não lidas permanecem na subscrição até serem processadas. Uma falha significativa é a latência entre o momento em que o evento é gerado e a operação de sondagem que puxa essa mensagem para o assinante para processamento. Além disso, a sobrecarga de sondagens constantes para o próximo evento consome recursos e dinheiro.
O EventGrid, no entanto, é diferente. Ele implementa um modelo de push no qual os eventos são enviados para os EventHandlers conforme recebidos, fornecendo entrega de eventos quase em tempo real. Também reduz os custos, uma vez que o serviço é acionado apenas quando é necessário consumir um evento – não continuamente, como acontece com a sondagem. Dito isso, um manipulador de eventos deve lidar com a carga de entrada e fornecer mecanismos de limitação para se proteger de ficar sobrecarregado. Muitos serviços do Azure que consomem esses eventos, como o Azure Functions e os Aplicativos Lógicos, fornecem recursos de dimensionamento automático automático para lidar com cargas maiores.
A Grade de Eventos é um serviço de nuvem sem servidor totalmente gerenciado. Ele é dimensionado dinamicamente com base no seu tráfego e cobra apenas pelo seu uso real, não pela capacidade pré-comprada. As primeiras 100.000 operações por mês são gratuitas – sendo as operações definidas como entrada de eventos (notificações de eventos de entrada), tentativas de entrega de assinaturas, chamadas de gerenciamento e filtragem por assunto. Com 99,99% de disponibilidade, o EventGrid garante a entrega de um evento dentro de um período de 24 horas, com funcionalidade de repetição integrada para entrega malsucedida. As mensagens não entregues podem ser movidas para uma fila de "letra morta" para resolução. Ao contrário do Barramento de Serviço do Azure, a Grade de Eventos é ajustada para um desempenho rápido e não oferece suporte a recursos como mensagens ordenadas, transações e sessões.
Streaming de mensagens na nuvem do Azure
O Barramento de Serviço do Azure e a Grade de Eventos fornecem um ótimo suporte para aplicativos que expõem eventos únicos e discretos, como se um novo documento tivesse sido inserido em um Cosmos DB. Mas, e se o seu sistema nativo da nuvem precisar processar um fluxo de eventos relacionados? Os fluxos de eventos são mais complexos. Eles geralmente são ordenados por tempo, inter-relacionados e devem ser processados como um grupo.
O Hub de Eventos do Azure é uma plataforma de streaming de dados e um serviço de ingestão de eventos que coleta, transforma e armazena eventos. Ele é ajustado para capturar dados de streaming, como notificações de eventos contínuos emitidos a partir de um contexto de telemetria. O serviço é altamente escalável e pode armazenar e processar milhões de eventos por segundo. Mostrado na Figura 4-18, geralmente é uma porta de entrada para um pipeline de eventos, dissociando o fluxo de ingestão do consumo de eventos.
Figura 4-18. Hub de Eventos do Azure
O Hub de Eventos suporta baixa latência e retenção de tempo configurável. Ao contrário de filas e tópicos, os Hubs de Eventos mantêm os dados do evento depois que eles são lidos por um consumidor. Esse recurso permite que outros serviços de análise de dados, tanto internos quanto externos, reproduzam os dados para análise posterior. Os eventos armazenados no hub de eventos só são excluídos após a expiração do período de retenção, que é de um dia por padrão, mas configurável.
O Hub de Eventos suporta protocolos comuns de publicação de eventos, incluindo HTTPS e AMQP. Ele também suporta Kafka 1.0. Os aplicativos Kafka existentes podem se comunicar com o Hub de Eventos usando o protocolo Kafka, fornecendo uma alternativa ao gerenciamento de grandes clusters Kafka. Muitos sistemas nativos da nuvem de código aberto adotam o Kafka.
Os Hubs de Eventos implementam o streaming de mensagens por meio de um modelo de consumidor particionado no qual cada consumidor lê apenas um subconjunto específico, ou partição, do fluxo de mensagens. Esse padrão permite uma enorme escala horizontal para o processamento de eventos e fornece outros recursos focados em fluxo que não estão disponíveis em filas e tópicos. Uma partição é uma sequência ordenada de eventos mantida num hub de eventos. À medida que novos eventos chegam, eles são adicionados ao final desta sequência. A Figura 4-19 mostra o particionamento em um Hub de Eventos.
Figura 4-19. Particionamento do Hub de Eventos
Em vez de ler a partir do mesmo recurso, cada grupo de consumidores lê através de um subconjunto, ou partição, do fluxo de mensagens.
Para aplicativos nativos da nuvem que devem transmitir um grande número de eventos, o Hub de Eventos do Azure pode ser uma solução robusta e acessível.