Design de Hubs de Eventos e Funções Resilientes

Tratamento de erros, design para idempotência e gerenciamento do comportamento de repetição são algumas das medidas críticas que você pode tomar para garantir que as funções acionadas pelos Hubs de Eventos sejam resilientes e capazes de lidar com grandes volumes de dados. Este artigo aborda esses conceitos cruciais e faz recomendações para soluções de streaming de eventos sem servidor.

O Azure fornece três serviços de mensagens principais que podem ser usados com o Azure Functions para dar suporte a uma ampla variedade de cenários exclusivos orientados a eventos. Devido ao seu modelo de consumidor particionado e à capacidade de ingerir dados a uma taxa elevada, os Hubs de Eventos do Azure são normalmente utilizados para cenários de streaming de eventos e big data. Para obter uma comparação detalhada dos serviços de mensagens do Azure, consulte Escolher entre serviços de mensagens do Azure - Grade de Eventos, Hubs de Eventos e Barramento de Serviço.

Benefícios e desafios do streaming

Compreender os benefícios e desvantagens dos fluxos ajuda você a apreciar como um serviço como os Hubs de Eventos opera. Você também precisa desse contexto ao tomar decisões arquitetônicas impactantes, solucionar problemas e otimizar o desempenho. Considere os seguintes conceitos-chave sobre soluções que apresentam Hubs de Eventos e Funções:

  • Fluxos não são filas: Hubs de Eventos, Kafka e outras ofertas semelhantes criadas no modelo de consumidor particionado não suportam intrinsecamente alguns dos principais recursos de um agente de mensagens como o Service Bus. Talvez o maior indicador dessas diferenças seja o fato de que as leituras não são destrutivas. Isso garante que os dados lidos pelo host Functions permaneçam disponíveis posteriormente. Em vez disso, as mensagens são imutáveis e permanecem para outros consumidores lerem, incluindo potencialmente o mesmo consumidor que as lê novamente. Por esse motivo, soluções que implementam padrões como consumidores concorrentes podem ser melhor servidas com um agente de mensagens, como o Service Bus.

  • Falta de suporte inerente a cartas mortas: um canal de letra morta não é um recurso nativo em Hubs de Eventos ou Kafka. Muitas vezes, o conceito de dead-lettering é integrado em uma solução de streaming para contabilizar dados que não podem ser processados. Essa funcionalidade não é intencionalmente um elemento inato nos Hubs de Eventos e só é adicionada no lado do consumidor para fabricar um comportamento ou efeito semelhante. Se você precisar de suporte por carta morta, você deve potencialmente rever sua escolha de um serviço de mensagens de streaming.

  • Uma unidade de trabalho é uma partição: em um agente de mensagens tradicional, uma unidade de trabalho é uma única mensagem. Em uma solução de streaming, uma partição é frequentemente considerada a unidade de trabalho. Se cada evento em um hub de eventos for tratado como uma mensagem distinta que requer processamento de pedidos ou processamento de transações financeiras, isso sugere uma oportunidade de explorar um serviço de mensagens mais adequado para um desempenho ou processamento ideais.

  • Sem filtragem do lado do servidor: uma das razões pelas quais os Hubs de Eventos são capazes de uma enorme escala e taxa de transferência é devido à baixa sobrecarga no próprio serviço. Recursos como filtragem do lado do servidor, índices e coordenação entre corretores não fazem parte da arquitetura dos Hubs de Eventos. As funções são ocasionalmente usadas para filtrar eventos, encaminhando-os para outros hubs de eventos com base no conteúdo no corpo ou cabeçalho. Essa abordagem é comum no streaming de eventos, mas vem com a ressalva de que a função inicial lê e avalia cada evento.

  • Cada leitor deve ler todos os dados: Como a filtragem do lado do servidor não está disponível, um consumidor lê sequencialmente todos os dados em uma partição. Isto inclui dados que podem não ser relevantes ou podem mesmo estar mal formados. Várias opções e estratégias podem ser usadas para compensar esses desafios, que serão abordadas mais adiante nesta seção.

Essas decisões significativas de design permitem que os Hubs de Eventos façam o que fazem melhor: dar suporte a um influxo significativo de eventos e fornecer um serviço robusto e resiliente para os consumidores lerem. Cada aplicativo de consumidor é encarregado da responsabilidade de manter seus próprios deslocamentos do lado do cliente ou cursor para esses eventos. A baixa sobrecarga torna os Hubs de Eventos uma opção acessível e poderosa para streaming de eventos.

Idempotência

Um dos princípios fundamentais dos Hubs de Eventos do Azure é o conceito de entrega pelo menos uma vez. Essa abordagem garante que os eventos sejam sempre entregues. Significa também que os eventos podem ser recebidos mais do que uma vez, mesmo repetidamente, pelos consumidores, tal como uma função. Por esse motivo, é importante que uma função acionada por hub de eventos suporte o padrão de consumidor idempotente.

Trabalhar sob o pressuposto de pelo menos uma entrega única, especialmente no contexto de uma arquitetura orientada a eventos, é uma abordagem responsável pelo processamento confiável de eventos. Sua função deve ser idempotente para que o resultado de processar o mesmo evento várias vezes seja o mesmo que processá-lo uma vez.

Eventos duplicados

Há vários cenários diferentes que podem resultar em eventos duplicados sendo entregues a uma função:

  • Ponto de verificação: se o host do Azure Functions falhar ou o limite definido para a frequência do ponto de verificação em lote não for atingido, um ponto de verificação não será criado. Como resultado, o deslocamento para o consumidor não é avançado e, na próxima vez que a função for invocada, ela será retomada a partir do último ponto de verificação. É importante notar que o checkpoint ocorre no nível da partição para cada consumidor.

  • Eventos duplicados publicados: muitas técnicas podem reduzir as chances de o mesmo evento ser publicado em um fluxo, mas o consumidor ainda é responsável por lidar com duplicatas idempotentes.

  • Confirmações ausentes: em algumas situações, uma solicitação de saída para um serviço pode ser bem-sucedida, no entanto, uma confirmação (ACK) do serviço nunca é recebida. Essa perceção pode resultar na crença de que a chamada de saída falhou e iniciar uma série de repetições ou outros resultados da função. No final, eventos duplicados podem ser publicados ou um ponto de verificação não é criado.

Técnicas de eliminação de duplicação

Projetar suas funções para entrada idêntica deve ser a abordagem padrão adotada quando emparelhada com a ligação de gatilho do Hub de Eventos. Você deve considerar as seguintes técnicas:

  • Procurando duplicatas: Antes de processar, execute as etapas necessárias para validar que o evento deve ser processado. Em alguns casos, isso requer uma investigação para confirmar que ainda é válido. Também pode ser possível que o tratamento do evento não seja mais necessário devido à atualização dos dados ou à lógica que invalida o evento.

  • Projetar eventos para idempotência: Ao fornecer informações adicionais dentro da carga útil do evento, é possível garantir que o processamento várias vezes não tenha efeitos prejudiciais. Tomemos o exemplo de um evento que inclui um montante para levantamento de uma conta bancária. Se não for tratado de forma responsável, é possível que possa diminuir o saldo de uma conta várias vezes. No entanto, se o mesmo evento incluir o saldo atualizado para a conta, ele pode ser usado para realizar uma operação de upsert para o saldo da conta bancária. Esta abordagem de transferência estatal transportada por eventos exige ocasionalmente coordenação entre produtores e consumidores e deve ser utilizada quando faz sentido para os serviços participantes.

Processamento de erros e tentativas

Tratamento de erros e novas tentativas são algumas das qualidades mais importantes de aplicativos distribuídos e orientados a eventos, e as funções não são exceção. Para soluções de streaming de eventos, a necessidade de suporte adequado para tratamento de erros é crucial, pois milhares de eventos podem se transformar rapidamente em um número igual de erros se não forem tratados corretamente.

Orientações para o tratamento de erros

Sem tratamento de erros, pode ser complicado implementar tentativas, detetar exceções de tempo de execução e investigar problemas. Cada função deve ter pelo menos algum nível ou tratamento de erros. Algumas diretrizes recomendadas são:

  • Use o Application Insights: habilite e use o Application Insights para registrar erros e monitorar a integridade de suas funções. Esteja atento às opções de amostragem configuráveis para cenários que processam um grande volume de eventos.

  • Adicionar tratamento estruturado de erros: aplique as construções apropriadas de tratamento de erros para cada linguagem de programação para capturar, registrar e detetar exceções antecipadas e não tratadas em seu código de função. Por exemplo, use um bloco try/catch em C#, Java e JavaScript e aproveite os blocos try e except em Python para lidar com exceções.

  • Registro em log: detetar uma exceção durante a execução oferece uma oportunidade de registrar informações críticas que podem ser usadas para detetar, reproduzir e corrigir problemas de forma confiável. Registre a exceção, não apenas a mensagem, mas o corpo, a exceção interna e outros artefatos úteis que serão úteis posteriormente.

  • Não pegue e ignore exceções: Uma das piores coisas que você pode fazer é pegar uma exceção e não fazer nada com ela. Se você pegar uma exceção genérica, registre-a em algum lugar. Se você não registrar erros, é difícil investigar bugs e problemas relatados.

Tentativas

A implementação da lógica de repetição em uma arquitetura de streaming de eventos pode ser complexa. Suportar tokens de cancelamento, contagens de repetições e estratégias exponenciais de recuo são apenas algumas das considerações que o tornam desafiador. Felizmente, o Functions fornece políticas de repetição que podem compensar muitas dessas tarefas que você normalmente codificaria por conta própria.

Vários fatores importantes que devem ser considerados ao usar as políticas de repetição com a associação do Hub de Eventos incluem:

  • Evite tentativas indefinidas: quando a configuração de contagem máxima de tentativas é definida como um valor de -1, a função tenta novamente indefinidamente. Em geral, as tentativas indefinidas devem ser usadas com moderação com Funções e quase nunca com a vinculação de gatilho do Hub de Eventos.

  • Escolha a estratégia de repetição apropriada: uma estratégia de atraso fixo pode ser ideal para cenários que recebem pressão de retorno de outros serviços do Azure. Nesses casos, o atraso pode ajudar a evitar a limitação e outras limitações encontradas nesses serviços. A estratégia de recuo exponencial oferece mais flexibilidade para intervalos de atraso de repetição e é comumente usada na integração com serviços de terceiros, pontos de extremidade REST e outros serviços do Azure.

  • Mantenha os intervalos e as contagens de tentativas baixas: Sempre que possível, tente manter um intervalo de repetição inferior a um minuto. Além disso, mantenha o número máximo de tentativas de repetição em um número razoavelmente baixo. Essas configurações são especialmente pertinentes quando executadas no plano de consumo de funções.

  • Padrão do disjuntor: um erro de falha transitória de tempos em tempos é esperado e um caso de uso natural para tentativas. No entanto, se um número significativo de falhas ou problemas estão ocorrendo durante o processamento da função, pode fazer sentido parar a função, resolver os problemas e reiniciar mais tarde.

Uma conclusão importante para as políticas de repetição no Functions é que ele é um recurso de melhor esforço para reprocessar eventos. Ele não substitui a necessidade de manipulação de erros, registro em log e outros padrões importantes que fornecem resiliência ao seu código.

Estratégias para falhas e dados corrompidos

Há várias abordagens notáveis que você pode usar para compensar problemas que surgem devido a falhas ou dados incorretos em um fluxo de eventos. Algumas estratégias fundamentais são:

  • Parar de enviar e ler: para corrigir o problema subjacente, pause a leitura e a gravação de eventos. O benefício dessa abordagem é que os dados não serão perdidos e as operações podem ser retomadas após a implementação de uma correção. Essa abordagem pode exigir um componente disjuntor na arquitetura e, possivelmente, uma notificação aos serviços afetados para obter uma pausa. Em alguns casos, a interrupção de uma função pode ser necessária até que os problemas sejam resolvidos.

  • Soltar mensagens: se as mensagens não forem importantes ou forem consideradas não críticas para a missão, considere seguir em frente e não processá-las. Essa abordagem não funciona para cenários que exigem forte consistência, como a gravação de movimentos em uma partida de xadrez ou transações baseadas em finanças. O tratamento de erros dentro de uma função é recomendado para capturar e soltar mensagens que não podem ser processadas.

  • Repetir: Existem muitas situações que podem justificar o reprocessamento de um evento. O cenário mais comum seria um erro transitório encontrado ao chamar outro serviço ou dependência. Erros de rede, limites e disponibilidade de serviço e forte consistência são talvez os casos de uso mais frequentes que justificam tentativas de reprocessamento.

  • Letra morta: A ideia aqui é publicar o evento em um hub de eventos diferente para que o fluxo existente não seja interrompido. A perceção é que ele é movido para fora do caminho quente e pode ser tratado mais tarde ou por um processo diferente. Esta solução é usada frequentemente para lidar com mensagens envenenadas ou eventos. Cada função configurada com um grupo de consumidores diferente ainda encontrará dados ruins ou corrompidos em seu fluxo e deve lidar com isso de forma responsável.

  • Repetição e letra morta: A combinação de inúmeras tentativas de repetição antes de finalmente publicar em um fluxo de letra morta uma vez que um limite é atingido, é outro método familiar.

  • Usar um registro de esquema: um registro de esquema pode ser usado como uma ferramenta proativa para ajudar a melhorar a consistência e a qualidade dos dados. O Registro de Esquema do Azure pode dar suporte à transição de esquemas, juntamente com o controle de versão e diferentes modos de compatibilidade à medida que os esquemas evoluem. Em sua essência, o esquema serve como um contrato entre produtores e consumidores, o que poderia reduzir a possibilidade de dados inválidos ou corrompidos serem publicados no fluxo.

No final, não há uma solução perfeita e as consequências e compensações de cada uma das estratégias precisam ser examinadas minuciosamente. Com base nos requisitos, usar várias dessas técnicas em conjunto pode ser a melhor abordagem.

Contribuidores

Este artigo é mantido pela Microsoft. Foi originalmente escrito pelos seguintes contribuidores.

Autor principal:

  • David Barkol - Brasil | Especialista Principal de Soluções GBB

Para ver perfis não públicos do LinkedIn, inicie sessão no LinkedIn.

Próximos passos

Antes de continuar, considere rever estes artigos relacionados:

O processamento de eventos sem servidor é uma arquitetura de referência que detalha uma arquitetura típica desse tipo, com exemplos de código e discussão de considerações importantes.