Processamento de eventos sem servidor

Azure Cosmos DB
Azure Functions
Azure Monitor
Azure Pipelines
Azure Storage

Essa arquitetura de referência mostra uma arquitetura sem servidor, orientada a eventos, que ingere um fluxo de dados, processa os dados e grava os resultados em um banco de dados back-end.

Arquitetura

Diagrama mostrando uma arquitetura de referência para processamento de eventos sem servidor usando o Azure Functions.

Fluxo de Trabalho

  • Os eventos chegam aos Hubs de Eventos do Azure.
  • Um aplicativo de função é acionado para manipular o evento.
  • O evento é armazenado em um banco de dados do Azure Cosmos DB.
  • Se o aplicativo de função não conseguir armazenar o evento com êxito, o evento será salvo em uma fila de armazenamento para ser processado posteriormente.

Componentes

  • Os Hubs de Eventos ingerem o fluxo de dados. Os Hubs de Eventos foram projetados para cenários de streaming de dados de alta taxa de transferência.

    Nota

    Para cenários de Internet das Coisas (IoT), recomendamos o Hub IoT do Azure. O Hub IoT tem um ponto de extremidade interno compatível com a API de Hubs de Eventos do Azure, portanto, você pode usar qualquer um dos serviços nessa arquitetura sem grandes alterações no processamento de back-end. Para obter mais informações, consulte Conectando dispositivos IoT ao Azure: Hub IoT e Hubs de Eventos.

  • Aplicativo de função. O Azure Functions é uma opção de computação sem servidor. Ele usa um modelo controlado por eventos, onde uma parte do código (uma função) é invocada por um gatilho. Nessa arquitetura, quando os eventos chegam aos Hubs de Eventos, eles acionam uma função que processa os eventos e grava os resultados no armazenamento.

    Os aplicativos de função são adequados para processar registros individuais de Hubs de Eventos. Para cenários de processamento de fluxo mais complexos, considere o Apache Spark usando o Azure Databricks ou o Azure Stream Analytics.

  • Azure Cosmos DB. O Azure Cosmos DB é um serviço de banco de dados multimodelo que está disponível em um modo sem servidor e baseado no consumo. Para esse cenário, a função de processamento de eventos armazena registros JSON, usando o Azure Cosmos DB para NoSQL.

  • Armazenamento em fila. O armazenamento em fila é usado para mensagens de letra morta. Se ocorrer um erro durante o processamento de um evento, a função armazenará os dados do evento em uma fila de mensagens mortas para processamento posterior. Para obter mais informações, consulte a seção Resiliência mais adiante neste artigo.

  • Azure Monitor. O Monitor coleta métricas de desempenho sobre os serviços do Azure implantados na solução. Ao visualizá-los em um painel, você pode obter visibilidade sobre a integridade da solução.

  • Azure Pipelines. Pipelines é um serviço de integração contínua (CI) e entrega contínua (CD) que cria, testa e implanta o aplicativo.

Considerações

Essas considerações implementam os pilares do Azure Well-Architected Framework, que é um conjunto de princípios orientadores que podem ser usados para melhorar a qualidade de uma carga de trabalho. Para obter mais informações, consulte Microsoft Azure Well-Architected Framework.

Disponibilidade

A implantação mostrada aqui reside em uma única região do Azure. Para uma abordagem mais resiliente à recuperação de desastres, aproveite os recursos de distribuição geográfica nos vários serviços:

  • os Hubs de Eventos. Crie dois namespaces de Hubs de Eventos, um namespace primário (ativo) e um namespace secundário (passivo). As mensagens são roteadas automaticamente para o namespace ativo, a menos que você faça failover para o namespace secundário. Para obter mais informações, consulte Recuperação de desastres geográficos dos Hubs de Eventos do Azure.
  • Aplicativo de função. Implante um segundo aplicativo de função que esteja aguardando leitura do namespace secundário dos Hubs de Eventos. Esta função grava em uma conta de armazenamento secundária para uma fila de mensagens mortas.
  • Azure Cosmos DB. O Azure Cosmos DB dá suporte a várias regiões de gravação, o que permite gravações em qualquer região que você adicionar à sua conta do Azure Cosmos DB. Se você não habilitar a gravação múltipla, ainda poderá fazer failover na região de gravação principal. Os SDKs de cliente do Azure Cosmos DB e as associações de Função do Azure lidam automaticamente com o failover, portanto, você não precisa atualizar nenhuma definição de configuração do aplicativo.
  • Armazenamento do Azure. Use o armazenamento RA-GRS para a fila de mensagens mortas. Isso cria uma réplica somente leitura em outra região. Se a região primária ficar indisponível, você poderá ler os itens atualmente na fila. Além disso, provisione outra conta de armazenamento na região secundária na qual a função pode gravar após um failover.

Escalabilidade

Hubs de Eventos

A capacidade de taxa de transferência dos Hubs de Eventos é medida em unidades de taxa de transferência. Você pode dimensionar automaticamente um hub de eventos habilitando a auto-inflação, que dimensiona automaticamente as unidades de taxa de transferência com base no tráfego, até um máximo configurado.

O gatilho de Hubs de Eventos no aplicativo de função é dimensionado de acordo com o número de partições no hub de eventos. A cada partição é atribuída uma instância de função de cada vez. Para maximizar a taxa de transferência, receba os eventos em um lote, em vez de um de cada vez.

Azure Cosmos DB

O Azure Cosmos DB está disponível em dois modos de capacidade diferentes:

  • Sem servidor, para cargas de trabalho com tráfego intermitente ou imprevisível e baixa relação de tráfego médio-pico.
  • Taxa de transferência provisionada, para cargas de trabalho com tráfego sustentado que exigem desempenho previsível.

Para garantir que sua carga de trabalho seja escalável, é importante escolher uma chave de partição apropriada ao criar seus contêineres do Azure Cosmos DB. Aqui estão algumas características de uma boa chave de partição:

  • O espaço de valor chave é grande.
  • Haverá uma distribuição uniforme de leituras/gravações por valor de chave, evitando teclas de atalho.
  • Os dados máximos armazenados para qualquer valor de chave única não excederão o tamanho máximo da partição física (20 GB).
  • A chave de partição de um documento não será alterada. Não é possível atualizar a chave de partição em um documento existente.

No cenário para essa arquitetura de referência, a função armazena exatamente um documento por dispositivo que está enviando dados. A função atualiza continuamente os documentos com o status mais recente do dispositivo usando uma operação de upsert. O ID do dispositivo é uma boa chave de partição para este cenário porque as gravações serão distribuídas uniformemente entre as chaves e o tamanho de cada partição será estritamente limitado porque há um único documento para cada valor de chave. Para obter mais informações sobre chaves de partição, consulte Particionar e dimensionar no Azure Cosmos DB.

Resiliência

Ao usar o gatilho de Hubs de Eventos com Funções, detete exceções dentro do seu loop de processamento. Se ocorrer uma exceção sem tratamento, o tempo de execução do Functions não repetirá as mensagens. Se uma mensagem não puder ser processada, coloque-a em uma fila de mensagens mortas. Use um processo fora de banda para examinar as mensagens e determinar a ação corretiva.

O código a seguir mostra como a função de ingestão captura exceções e coloca mensagens não processadas em uma fila de mensagens mortas.

 [Function(nameof(RawTelemetryFunction))]
 public async Task RunAsync([EventHubTrigger("%EventHubName%", Connection = "EventHubConnection")] EventData[] messages,
     FunctionContext context)
 {
     _telemetryClient.GetMetric("EventHubMessageBatchSize").TrackValue(messages.Length);
     DeviceState? deviceState = null;
     // Create a new CosmosClient
     var cosmosClient = new CosmosClient(Environment.GetEnvironmentVariable("COSMOSDB_CONNECTION_STRING"));

     // Get a reference to the database and the container
     var database = cosmosClient.GetDatabase(Environment.GetEnvironmentVariable("COSMOSDB_DATABASE_NAME"));
     var container = database.GetContainer(Environment.GetEnvironmentVariable("COSMOSDB_DATABASE_COL"));

     // Create a new QueueClient
     var queueClient = new QueueClient(Environment.GetEnvironmentVariable("DeadLetterStorage"), "deadletterqueue");
     await queueClient.CreateIfNotExistsAsync();

     foreach (var message in messages)
     {
         try
         {
             deviceState = _telemetryProcessor.Deserialize(message.Body.ToArray(), _logger);
             try
             {
                 // Add the device state to Cosmos DB
                 await container.UpsertItemAsync(deviceState, new PartitionKey(deviceState.DeviceId));
             }
             catch (Exception ex)
             {
                  _logger.LogError(ex, "Error saving on database", message.PartitionKey, message.SequenceNumber);
                 var deadLetterMessage = new DeadLetterMessage { Issue = ex.Message, MessageBody = message.Body.ToArray(), DeviceState = deviceState };
                 // Convert the dead letter message to a string
                 var deadLetterMessageString = JsonConvert.SerializeObject(deadLetterMessage);

                 // Send the message to the queue
                 await queueClient.SendMessageAsync(deadLetterMessageString);
             }

         }
         catch (Exception ex)
         {
             _logger.LogError(ex, "Error deserializing message", message.PartitionKey, message.SequenceNumber);
             var deadLetterMessage = new DeadLetterMessage { Issue = ex.Message, MessageBody = message.Body.ToArray(), DeviceState = deviceState };
             // Convert the dead letter message to a string
             var deadLetterMessageString = JsonConvert.SerializeObject(deadLetterMessage);

             // Send the message to the queue
             await queueClient.SendMessageAsync(deadLetterMessageString);
         }
     }
 }

O código mostrado também registra exceções no Application Insights. Você pode usar a chave de partição e o número de sequência para correlacionar mensagens de letra morta com as exceções nos logs.

As mensagens na fila de mensagens mortas devem ter informações suficientes para que você possa entender o contexto do erro. Neste exemplo, a DeadLetterMessage classe contém a mensagem de exceção, os dados do corpo do evento original e a mensagem de evento desserializada (se disponível).

    public class DeadLetterMessage
    {
        public string? Issue { get; set; }
        public byte[]? MessageBody { get; set; }
        public DeviceState? DeviceState { get; set; }
    }

Use o Azure Monitor para monitorar o hub de eventos. Se você vir que há entrada, mas não saída, isso significa que as mensagens não estão sendo processadas. Nesse caso, entre no Log Analytics e procure exceções ou outros erros.

DevOps

Use a infraestrutura como código (IaC) sempre que possível. O IaC gerencia a infraestrutura, o aplicativo e os recursos de armazenamento com uma abordagem declarativa como o Azure Resource Manager. Isso ajudará a automatizar a implantação usando o DevOps como uma solução de integração contínua e entrega contínua (CI/CD). Os modelos devem ser versionados e incluídos como parte do pipeline de versão.

Ao criar modelos, agrupe recursos como forma de organizá-los e isolá-los por carga de trabalho. Uma maneira comum de pensar sobre a carga de trabalho é um único aplicativo sem servidor ou uma rede virtual. O objetivo do isolamento da carga de trabalho é associar os recursos a uma equipe, para que a equipe de DevOps possa gerenciar de forma independente todos os aspetos desses recursos e executar CI/CD.

À medida que você implanta seus serviços, você precisará monitorá-los. Considere o uso do Application Insights para permitir que os desenvolvedores monitorem o desempenho e detetem problemas.

Recuperação após desastre

A implantação mostrada aqui reside em uma única região do Azure. Para uma abordagem mais resiliente à recuperação de desastres, aproveite os recursos de distribuição geográfica nos vários serviços:

  • Hubs de Eventos. Crie dois namespaces de Hubs de Eventos, um namespace primário (ativo) e um namespace secundário (passivo). As mensagens são roteadas automaticamente para o namespace ativo, a menos que você faça failover para o namespace secundário. Para obter mais informações, consulte Recuperação de desastres geográficos dos Hubs de Eventos do Azure.

  • Aplicativo de função. Implante um segundo aplicativo de função que esteja aguardando leitura do namespace secundário dos Hubs de Eventos. Esta função grava em uma conta de armazenamento secundária para fila de mensagens mortas.

  • Azure Cosmos DB. O Azure Cosmos DB dá suporte a várias regiões de gravação, o que permite gravações em qualquer região que você adicionar à sua conta do Azure Cosmos DB. Se você não habilitar a gravação múltipla, ainda poderá fazer failover na região de gravação principal. Os SDKs de cliente do Azure Cosmos DB e as associações de Função do Azure lidam automaticamente com o failover, portanto, você não precisa atualizar nenhuma definição de configuração do aplicativo.

  • Armazenamento do Azure. Use o armazenamento RA-GRS para a fila de mensagens mortas. Isso cria uma réplica somente leitura em outra região. Se a região primária ficar indisponível, você poderá ler os itens atualmente na fila. Além disso, provisione outra conta de armazenamento na região secundária na qual a função pode gravar após um failover.

Otimização de custos

A otimização de custos consiste em procurar formas de reduzir despesas desnecessárias e melhorar a eficiência operacional. Para obter mais informações, consulte Visão geral do pilar de otimização de custos.

Use a calculadora de preços do Azure para estimar custos. Aqui estão algumas outras considerações para o Azure Functions e o Azure Cosmos DB.

Funções do Azure

O Azure Functions suporta dois modelos de alojamento:

  • Plano de consumo. O poder de computação é alocado automaticamente quando o código está em execução.
  • Plano do Serviço de Aplicações. Um conjunto de máquinas virtuais (VMs) são alocadas para seu código. O plano do Serviço de Aplicativo define o número de VMs e o tamanho da VM.

Nessa arquitetura, cada evento que chega aos Hubs de Eventos dispara uma função que processa esse evento. Do ponto de vista dos custos, a recomendação é usar o plano de consumo porque você paga apenas pelos recursos de computação que usa.

Azure Cosmos DB

Com o Azure Cosmos DB, paga pelas operações que realiza na base de dados e pelo armazenamento consumido pelos seus dados.

  • Operações de banco de dados. A maneira como você é cobrado por suas operações de banco de dados depende do tipo de conta do Azure Cosmos DB que você está usando.
    • No modo sem servidor, você não precisa provisionar nenhuma taxa de transferência ao criar recursos em sua conta do Azure Cosmos DB. No final do período de faturamento, você será cobrado pela quantidade de Unidades de Solicitação consumidas pelas operações do banco de dados.
    • No modo de taxa de transferência provisionada, você especifica a taxa de transferência necessária em Unidades de solicitação por segundo (RU/s) e é cobrado por hora pela taxa de transferência máxima provisionada para uma determinada hora. Observação: como o modelo de taxa de transferência provisionada dedica recursos ao seu contêiner ou banco de dados, você será cobrado pela taxa de transferência provisionada, mesmo que não execute nenhuma carga de trabalho.
  • Armazenamento. É cobrada uma taxa fixa pela quantidade total de armazenamento (em GBs) consumida pelos seus dados e índices durante uma determinada hora.

Nessa arquitetura de referência, a função armazena exatamente um documento por dispositivo que está enviando dados. A função atualiza continuamente os documentos com o status mais recente do dispositivo, usando uma operação de upsert, que é rentável em termos de armazenamento consumido. Para obter mais informações, consulte Modelo de preços do Azure Cosmos DB.

Use a calculadora de capacidade do Azure Cosmos DB para obter uma estimativa rápida do custo da carga de trabalho.

Implementar este cenário

Logótipo do GitHub Uma implementação de referência para essa arquitetura está disponível no GitHub.

Próximos passos