Padrão de nivelamento de carga baseado em fila

Use uma fila que atua como um buffer entre uma tarefa e o serviço que ela invoca. Essa abordagem suaviza cargas pesadas intermitentes que podem fazer com que o serviço falhe ou que a tarefa tenha um tempo limite. Ele ajuda a minimizar o efeito dos picos de demanda na disponibilidade e na capacidade de resposta da tarefa e do serviço.

Contexto e problema

Muitas soluções na nuvem executam tarefas que invocam serviços. Nesse ambiente, cargas pesadas intermitentes podem causar problemas de desempenho ou confiabilidade para um serviço.

Um serviço pode fazer parte da mesma solução que as tarefas que o usam ou pode ser um serviço de parceiro que fornece acesso a recursos usados com frequência. Exemplos desses tipos de serviços incluem um cache ou um serviço de armazenamento. Quando várias tarefas são executadas simultaneamente e usam o mesmo serviço, é difícil prever o volume de solicitações a qualquer momento.

Um serviço pode experimentar picos de demanda que o sobrecarregam e tornam o serviço incapaz de responder às solicitações rapidamente. Sobrecarregar um serviço com muitas solicitações simultâneas também pode fazer com que ele falhe se não conseguir lidar com a contenção que essas solicitações causam.

Solução

Coloque uma fila entre a tarefa e o serviço. A tarefa e o serviço são executados de maneira assíncrona. A tarefa envia para a fila uma mensagem que contém os dados de que o serviço necessita. A fila atua como um buffer e armazena a mensagem até que o serviço a recupere. O serviço recupera mensagens da fila e as processa. As solicitações de várias tarefas, que podem ser geradas a taxas altamente variáveis, podem ser passadas para o serviço por meio da mesma fila de mensagens. O diagrama a seguir mostra como uma fila pode nivelar a carga em um serviço.

Diagrama que mostra como uma fila de mensagens atua como um buffer entre tarefas e um serviço.

A fila desassocia as tarefas do serviço para que o serviço possa lidar com as mensagens em seu próprio ritmo, mesmo quando tarefas simultâneas geram um alto volume de solicitações. Além disso, as tarefas não serão atrasadas se o serviço não estiver disponível quando postarem mensagens na fila.

Esse padrão proporciona os seguintes benefícios:

  • Isso ajuda a maximizar a disponibilidade porque os atrasos no serviço não afetam imediata e diretamente a aplicação. O aplicativo pode continuar postando mensagens na fila mesmo quando o serviço não está disponível ou não está processando mensagens no momento.

  • Ele ajuda a maximizar a escalabilidade porque o número de filas e o número de serviços podem variar para atender à demanda.

  • Ele ajuda a controlar os custos porque você só precisa de instâncias de serviço suficientes para atender aos requisitos de uma carga média em vez da carga de pico.

Note

Alguns serviços implementam a limitação quando a demanda atinge um limite que pode causar falha no sistema. A limitação pode reduzir a funcionalidade disponível. Implemente o nivelamento de carga nesses serviços para garantir que a demanda não atinja esse limite.

Problemas e considerações

Considere os seguintes pontos ao decidir como implementar esse padrão:

  • Implemente a lógica do aplicativo que controla a taxa na qual os serviços lidam com mensagens para evitar sobrecarregar o recurso de destino. Evite passar picos de demanda para o próximo estágio do sistema. Teste o sistema sob carga para garantir que ele forneça o nivelamento necessário. Para obter o nivelamento necessário, ajuste o número de filas e o número de instâncias de serviço que manipulam mensagens.

  • Filas de mensagens são um mecanismo de comunicação unidirecional. Se uma tarefa espera uma resposta de um serviço, talvez seja necessário implementar um mecanismo que o serviço pode usar para enviar uma resposta. Para obter mais informações, consulte Opções de mensagens assíncronas no Azure.

  • Escalonamento automático sem limitar a taxa agregada de saída dos consumidores apenas transfere a sobrecarga para dependências posteriores. Essa sobrecarga pode aumentar a contenção por recursos compartilhados por esses serviços e diminuir a eficácia da fila para distribuir a carga.

  • Se a taxa média de produção exceder a taxa de consumo, a fila continuará crescendo e a latência aumentará. Monitore a profundidade da fila e escale os consumidores dentro de limites seguros, ou reduza a carga no produtor.

  • Esse padrão depende da durabilidade da fila para evitar a perda de mensagens. Se o broker não persistir as mensagens em armazenamento durável, uma falha ou um limite de capacidade poderá fazer com que os dados enfileirados sejam perdidos antes que os consumidores os processem. Escolha um serviço de fila que persista as mensagens em disco ou em armazenamento replicado e entenda suas cotas de tamanho e limites de retenção. Para cargas de trabalho que exigem mensagens para sobreviver a falhas regionais, avalie as opções de recuperação de desastre geográfico.

  • A maioria dos serviços de fila entrega mensagens com semântica pelo menos uma vez, o que significa que os consumidores podem receber a mesma mensagem mais de uma vez. Projete a lógica do consumidor para que seja idempotente, de modo que o processamento da mesma mensagem várias vezes gere o mesmo resultado e evite problemas como registros duplicados ou cobranças duplicadas.

  • Algumas mensagens não podem ser processadas porque contêm dados malformados, fazem referência a recursos ausentes ou disparam erros persistentes. Em vez de deixar essas mensagens circularem indefinidamente e bloquearem a fila, encaminhe-as para uma fila de mensagens mortas. Monitore a profundidade da fila de mensagens mortas para que sua equipe de operações possa investigar falhas, corrigir o problema subjacente e reenviar mensagens quando apropriado.

  • A introdução de uma fila entre um produtor e um consumidor não preserva a ordem de envio original em todas as condições, especialmente quando vários consumidores processam mensagens em paralelo. Se sua carga de trabalho exigir ordenação estrita, use recursos como sessões message em Barramento de Serviço do Azure. Se a ordenação estrita não for necessária, projete os consumidores para lidar com mensagens em qualquer ordem, o que simplifica o dimensionamento.

Quando usar esse padrão

Use esse padrão quando:

  • Sua carga de trabalho experimenta picos intermitentes que podem sobrecarregar os serviços downstream.

  • Você precisa desacoplar a entrada de solicitações da capacidade de processamento para melhorar a resiliência e o controle de custos.

Esse padrão pode não ser adequado quando:

  • O chamador requer uma resposta síncrona e de baixa latência.

  • O volume de carga de trabalho é previsívelmente baixo e estável, portanto, adicionar complexidade de fila oferece pouco benefício.

Design de carga de trabalho

Avalie como usar o padrão de nivelamento de carga baseado em fila no design de uma carga de trabalho para atender às metas e aos princípios abordados nos pilares do Azure Well-Architected Framework. A tabela a seguir fornece diretrizes sobre como esse padrão dá suporte às metas de cada pilar.

Pilar Como esse padrão apoia os objetivos do pilar
As decisões de design de confiabilidade ajudam sua carga de trabalho a se tornar resiliente ao mau funcionamento e garantir que ela se recupere para um estado totalmente funcional após a ocorrência de uma falha. A abordagem descrita por esse padrão pode fornecer resiliência contra picos repentinos na demanda, desassociando a chegada de tarefas de seu processamento. Ele também pode isolar falhas no processamento de fila para que elas não afetem a entrada.

- RE:06 Dimensionamento
A Otimização de Custos concentra-se na manutenção e na melhoria do retorno sobre o investimento da carga de trabalho. Como o processamento de carga é desacoplado da solicitação ou da entrada de tarefas, você pode usar essa abordagem para reduzir a necessidade de provisionar recursos em excesso para lidar com a carga de pico.

- CO:12 Custos do dimensionamento
A Eficiência de Desempenho ajuda sua carga de trabalho a atender com eficiência às demandas por meio de otimizações no dimensionamento, nos dados e no código. Essa abordagem permite o design intencional para o desempenho da taxa de transferência porque a ingestão de solicitações não precisa se correlacionar com a taxa de processamento.

- PE:05 Dimensionamento e particionamento

Se esse padrão introduzir compensações dentro de um pilar, considere-as em relação aos objetivos dos outros pilares.

Example

Um aplicativo Web grava dados em um repositório de dados externo. Se várias instâncias do aplicativo web forem executadas simultaneamente, o armazenamento de dados poderá não conseguir responder às solicitações com rapidez suficiente, fazendo com que elas atinjam o tempo limite, tenham sua taxa limitada ou falhem de outra forma. O diagrama a seguir mostra um armazenamento de dados sobrecarregado por solicitações simultâneas de instâncias de um aplicativo.

Diagrama que mostra várias solicitações simultâneas de instâncias de um aplicativo Web sobrecarregando um serviço.

Para resolver esse problema, use uma fila para nivelar a carga entre as instâncias do aplicativo e o armazenamento de dados. Um aplicativo Azure Functions lê mensagens de uma fila de Barramento de Serviço e executa as solicitações de leitura/gravação no armazenamento de dados. O Azure Functions pode dimensionar instâncias com base no acúmulo de mensagens no Barramento de Serviço usando o dimensionamento baseado em metas, dentro dos limites de dimensionamento que você configurou. Você também pode ajustar as configurações de simultaneidade do gatilho para proteger o armazenamento de dados. Para obter diretrizes de implementação, consulte dimensionamento baseado em destino e expansão de limite. Sem esse ajuste, a camada de trabalho pode reintroduzir a contenção de back-end.

Diagrama que mostra como usar uma fila e um aplicativo de funções para nivelar a carga.

Como variação de tecnologia, você pode implementar o mesmo padrão usando Aplicativos de Contêiner do Azure em vez de Azure Functions. Nessa abordagem, um worker em contêiner consome mensagens do Barramento de Serviço e grava no armazenamento de dados. O Container Apps dimensiona o worker entre o número mínimo e máximo de réplicas configurado, com base em regras de escalonamento relacionadas à fila. Você também pode implementar a mesma abordagem usando Armazenamento de Filas do Azure como a origem do evento. Para obter diretrizes de implementação, consulte Definir regras de dimensionamento em Aplicativos de Contêiner e Implantar um trabalho controlado por eventos usando Aplicativos de Contêiner.

Próximas Etapas 

As diretrizes a seguir também podem ser relevantes ao implementar esse padrão:

  • Opções de mensagens assíncronas no Azure: as filas de mensagens são inerentemente assíncronas. Talvez seja necessário redesenhar a lógica de aplicação de uma tarefa caso ela se comunique diretamente com um serviço. Da mesma forma, talvez seja necessário refatorar um serviço para aceitar solicitações de uma fila de mensagens.

  • Escolha entre os serviços de mensagens do Azure: Obtenha mais informações para ajudar você a escolher um mecanismo de mensagens e filas em aplicativos do Azure.

  • Recomendações para desenvolver trabalhos em segundo plano: aplique esse padrão a trabalhos em segundo plano para que as filas de mensagens possam armazenar solicitações para tarefas em segundo plano quando o aplicativo tiver alta carga.

  • Estilo de arquitetura Web-Queue-Worker: a Web e o worker não mantêm estado. O estado de sessão pode ser armazenado em um cache distribuído. O worker executa tarefas de longa duração de forma assíncrona e pode ser acionado por mensagens na fila ou executado conforme um agendamento para processamento em lote.

  • Padrão de Consumidores Concorrentes: Pode ser possível executar várias instâncias de um serviço, cada uma atuando como consumidora de mensagens da fila de nivelamento de carga. Você pode usar essa abordagem para ajustar a taxa à qual as mensagens são recebidas e passadas para um serviço.

  • Padrão de limitação: uma maneira simples de implementar a limitação em um serviço é usar o nivelamento de carga baseado em fila e rotear todas as solicitações para um serviço por meio de uma fila de mensagens. O serviço pode processar solicitações em uma taxa que garante que não esgote os recursos de que precisa e que reduza a possibilidade de contenção.