Funções do Azure processamento de eventos fiável

O processamento de eventos é um dos cenários mais comuns associados à arquitetura sem servidor. Este artigo descreve como criar um processador de mensagens fiável com Funções do Azure para evitar perder mensagens.

Desafios dos fluxos de eventos em sistemas distribuídos

Considere um sistema que envia eventos a uma taxa constante de 100 eventos por segundo. A este ritmo, em poucos minutos, várias instâncias paralelas de Funções podem consumir os 100 eventos recebidos a cada segundo.

No entanto, qualquer uma das seguintes condições menos ideais é possível:

  • E se o publicador de eventos enviar um evento danificado?
  • E se a instância das Funções encontrar exceções não processadas?
  • E se um sistema a jusante ficar offline?

Como lida com estas situações ao preservar o débito da sua aplicação?

Com as filas, as mensagens fiáveis vêm naturalmente. Quando emparelhada com um acionador de Funções, a função cria um bloqueio na mensagem de fila. Se o processamento falhar, o bloqueio é libertado para permitir que outra instância repita o processamento. Em seguida, o processamento continua até que a mensagem seja avaliada com êxito ou seja adicionada a uma fila de veneno.

Mesmo que uma única mensagem de fila possa permanecer num ciclo de repetição, outras execuções paralelas continuam a impedir a colocação em fila de mensagens restantes. O resultado é que o débito geral permanece praticamente inafectado por uma mensagem incorreta. No entanto, as filas de armazenamento não garantem a ordenação e não estão otimizadas para as exigências de débito elevado exigidas pelos Hubs de Eventos.

Por outro lado, Hubs de Eventos do Azure não inclui um conceito de bloqueio. Para permitir funcionalidades como débito elevado, vários grupos de consumidores e capacidade de repetição, os eventos dos Hubs de Eventos comportam-se mais como um leitor de vídeo. Os eventos são lidos a partir de um único ponto no fluxo por partição. A partir do ponteiro, pode ler para a frente ou para trás a partir dessa localização, mas tem de optar por mover o ponteiro para que os eventos processem.

Quando ocorrem erros num fluxo, se decidir manter o ponteiro no mesmo local, o processamento de eventos é bloqueado até que o ponteiro seja avançado. Por outras palavras, se o ponteiro for parado para lidar com problemas de processamento de um único evento, os eventos não processados começam a acumular-se.

Funções do Azure evita impasses ao avançar o ponteiro do fluxo, independentemente do êxito ou da falha. Uma vez que o ponteiro continua a avançar, as suas funções têm de lidar adequadamente com as falhas.

Como Funções do Azure consome eventos dos Hubs de Eventos

Funções do Azure consome eventos do Hub de Eventos enquanto percorre os seguintes passos:

  1. É criado e mantido um ponteiro no Armazenamento do Azure para cada partição do hub de eventos.
  2. Quando são recebidas novas mensagens (num lote por predefinição), o anfitrião tenta acionar a função com o lote de mensagens.
  3. Se a função concluir a execução (com ou sem exceção), o ponteiro avança e é guardado um ponto de verificação na conta de armazenamento.
  4. Se as condições impedirem a conclusão da execução da função, o anfitrião não conseguirá progredir no ponteiro. Se o ponteiro não estiver avançado, as verificações posteriores acabarão por processar as mesmas mensagens.
  5. Repita os passos 2 a 4

Este comportamento revela alguns pontos importantes:

Processamento de exceções

Regra geral, cada função deve incluir um bloco try/catch no nível mais elevado de código. Especificamente, todas as funções que consomem eventos dos Hubs de Eventos devem ter um catch bloco. Dessa forma, quando é gerada uma exceção, o bloco catch processa o erro antes de o ponteiro progredir.

Mecanismos e políticas de repetição

Algumas exceções são transitórias por natureza e não reaparecem quando uma operação é tentada novamente momentos depois. É por isso que o primeiro passo é sempre repetir a operação. Pode tirar partido das políticas de repetição da aplicação de funções ou criar a lógica de repetição na execução da função.

A introdução de comportamentos de processamento de falhas nas suas funções permite-lhe definir políticas de repetição básicas e avançadas. Por exemplo, pode implementar uma política que siga um fluxo de trabalho ilustrado pelas seguintes regras:

  • Tente inserir uma mensagem três vezes (potencialmente com um atraso entre repetições).
  • Se o resultado eventual de todas as repetições for uma falha, adicione uma mensagem a uma fila para que o processamento possa continuar no fluxo.
  • As mensagens danificadas ou não processadas são processadas posteriormente.

Nota

O Polly é um exemplo de uma biblioteca de resiliência e processamento de falhas transitórias para aplicações C#.

Erros de não exceção

Alguns problemas surgem mesmo quando um erro não está presente. Por exemplo, considere uma falha que ocorre no meio de uma execução. Neste caso, se uma função não concluir a execução, o ponteiro de deslocamento nunca será progredido. Se o ponteiro não avançar, qualquer instância que seja executada após uma execução falhada continuará a ler as mesmas mensagens. Esta situação fornece uma garantia "pelo menos uma vez".

A garantia de que cada mensagem é processada pelo menos uma vez implica que algumas mensagens podem ser processadas mais do que uma vez. As suas aplicações de funções têm de estar cientes desta possibilidade e têm de ser criadas em torno dos princípios da idempotência.

Parar e reiniciar a execução

Embora alguns erros possam ser aceitáveis, e se a sua aplicação tiver falhas significativas? Poderá querer parar de acionar eventos até que o sistema atinja um bom estado de funcionamento. Ter a oportunidade de colocar o processamento em pausa é muitas vezes alcançado com um padrão de disjuntor automático. O padrão de disjuntor automático permite que a sua aplicação "quebre o circuito" do processo de evento e seja retomada mais tarde.

Existem duas partes necessárias para implementar um disjuntor automático num processo de evento:

  • Estado partilhado em todas as instâncias para controlar e monitorizar o estado de funcionamento do circuito
  • Processo principal que pode gerir o estado do circuito (aberto ou fechado)

Os detalhes de implementação podem variar, mas para partilhar o estado entre instâncias, precisa de um mecanismo de armazenamento. Pode optar por armazenar o estado no Armazenamento do Azure, numa cache de Redis ou em qualquer outra conta que esteja acessível por uma coleção de funções.

O Azure Logic Apps ou as funções duráveis são uma opção natural para gerir o fluxo de trabalho e o estado do circuito. Outros serviços também podem funcionar, mas as aplicações lógicas são utilizadas para este exemplo. Com as aplicações lógicas, pode colocar em pausa e reiniciar a execução de uma função, dando-lhe o controlo necessário para implementar o padrão de disjuntor automático.

Definir um limiar de falha entre instâncias

Para contabilizar vários eventos de processamento de instâncias em simultâneo, é necessário manter o estado externo partilhado para monitorizar o estado de funcionamento do circuito.

Uma regra que pode optar por implementar pode impor que:

  • Se existirem mais de 100 falhas eventualmente em 30 segundos em todas as instâncias, quebre o circuito e pare de acionar em novas mensagens.

Os detalhes de implementação variam consoante as suas necessidades, mas, em geral, pode criar um sistema que:

  1. Falhas de registo numa conta de armazenamento (Armazenamento do Azure, Redis, etc.)
  2. Quando for registada uma nova falha, inspecione a contagem de interrupções para ver se o limiar é atingido (por exemplo, mais de 100 nos últimos 30 segundos).
  3. Se o limiar for atingido, emita um evento para Azure Event Grid indicando ao sistema para interromper o circuito.

Gerir o estado do circuito com o Azure Logic Apps

A seguinte descrição realça uma forma de criar uma Aplicação Lógica do Azure para parar o processamento de uma aplicação de Funções.

O Azure Logic Apps inclui conectores incorporados para diferentes serviços, apresenta orquestrações com monitorização de estado e é uma opção natural para gerir o estado do circuito. Depois de detetar que o circuito tem de ser interrompido, pode criar uma aplicação lógica para implementar o seguinte fluxo de trabalho:

  1. Acionar um fluxo de trabalho do Event Grid e parar a Função do Azure (com o conector de Recursos do Azure)
  2. Enviar um e-mail de notificação que inclui uma opção para reiniciar o fluxo de trabalho

O destinatário do e-mail pode investigar o estado de funcionamento do circuito e, quando apropriado, reiniciar o circuito através de uma ligação no e-mail de notificação. À medida que o fluxo de trabalho reinicia a função, as mensagens são processadas a partir do último ponto de verificação do Hub de Eventos.

Ao utilizar esta abordagem, não são perdidas mensagens, todas as mensagens são processadas por ordem e pode interromper o circuito enquanto for necessário.

Recursos

Passos seguintes

Para obter mais informações, veja os seguintes recursos: