Padrão de nivelamento de carga baseado em fila

Funções do Azure
Barramento de Serviço do Azure

Use uma fila que atua como um buffer entre uma tarefa e um serviço que ela invoca para suavizar cargas pesadas intermitentes que podem fazer com que o serviço falhe ou que a tarefa tenha um tempo limite. Isso ajuda a minimizar o impacto 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 envolvem a execução de tarefas que invocam serviços. Nesse ambiente, se um serviço estiver sujeito a cargas pesadas intermitentes, isso poderá causar problemas de desempenho ou confiabilidade.

Um serviço pode ser parte da mesma solução que as tarefas que o utilizam ou pode ser um serviço de terceiros fornecendo acesso a recursos usados com frequência, como cache ou serviço de armazenamento. Se o mesmo serviço for usado por várias tarefas em execução ao mesmo tempo, poderá ser difícil prever o volume de solicitações para o serviço a qualquer momento.

Um serviço pode apresentar picos de demanda que causam a sobrecarga, não conseguindo responder às solicitações no tempo adequado. Inundar um serviço com um grande número de solicitações simultâneas também pode resultar em falha do serviço se não for possível tratar a contenção que essas solicitações causam.

Solução

Refatore a solução e introduza 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 uma mensagem que contém os dados exigidos pelo serviço a uma fila. A fila atua como um buffer, armazenando a mensagem até que ela seja recuperada pelo serviço. O serviço recupera as mensagens da fila e as processa. Solicitações de várias tarefas, que podem ser geradas a uma taxa altamente variável, podem ser passadas para o serviço pela mesma fila de mensagens. Esta figura mostra o uso de uma fila para nivelar a carga em um serviço.

Figura 1 – Como usar uma fila para nivelar a carga em um serviço

A fila separa as tarefas do serviço e o serviço pode manipular as mensagens em seu próprio ritmo, independentemente do volume de solicitações de tarefas simultâneas. Além disso, não haverá nenhum atraso para uma tarefa se o serviço não estiver disponível no momento em que ele envia uma mensagem à fila.

Esse padrão proporciona os seguintes benefícios:

  • Pode ajudar a maximizar a disponibilidade porque atrasos decorrentes de serviços não terão um impacto direto e imediato sobre o aplicativo, que pode continuar a postar mensagens na fila mesmo quando o serviço não está disponível ou atualmente não está processando mensagens.

  • Pode ajudar a maximizar a escalabilidade, pois tanto o número de filas quanto o número de serviços pode ser variado para atender à demanda.

  • Pode ajudar a controlar os custos, pois o número de instâncias de serviço implantadas só precisa ser suficiente para atender a carga média, em vez da carga de pico.

    Alguns serviços implementam limitação quando a demanda atinge um limite além do qual o sistema poderia falhar. A limitação pode reduzir a funcionalidade disponível. Você pode implementar nivelamento de carga com esses serviços para garantir que esse limite não seja atingido.

Problemas e considerações

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

  • É necessário implementar lógica de aplicativo que controle a taxa à qual os serviços processam as 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 ofereça o nivelamento necessário e ajuste o número de filas e o número de instâncias de serviço que manipulam mensagens para conseguir isso.
  • Filas de mensagens são um mecanismo de comunicação unidirecional. Se uma tarefa espera uma resposta de um serviço, pode ser necessário implementar um mecanismo que o serviço pode usar para enviar uma resposta. Para obter mais informações, consulte o Primer de mensagens assíncronas.
  • Tenha cuidado se você aplicar o dimensionamento automático a serviços que estão escutando solicitações na fila. Isso pode resultar em maior contenção para quaisquer recursos que esses serviços compartilhem e reduzir a eficiência do uso da fila para nivelar a carga.
  • Dependendo da carga do serviço, você pode se deparar com uma situação em que você está efetivamente sempre ficando para trás, onde o sistema está sempre enfileirando mais solicitações do que você está processando. A variabilidade do tráfego de entrada para seu aplicativo precisa ser levada em consideração
  • O padrão pode perder informações dependendo da persistência da fila. Se sua fila travar ou soltar informações (devido aos limites do sistema), existe a possibilidade de você não ter uma entrega garantida. O comportamento da fila e dos limites do sistema precisa ser levado em consideração com base nas necessidades da sua solução.

Quando usar esse padrão

Esse padrão é útil para qualquer aplicativo que use serviços sujeitos a sobrecarga.

Esse padrão não é útil se o aplicativo esperar uma resposta do serviço com latência mínima.

Design de carga de trabalho

Um arquiteto deve avaliar como o padrão de Nivelamento de Carga Baseado em Fila pode ser usado no design de sua carga de trabalho para abordar as metas e os princípios abordados nos pilares do Azure Well-Architected Framework. Por exemplo:

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 a garantir que ela se recupere para um estado totalmente funcional após a ocorrência de uma falha. A abordagem descrita neste padrão pode fornecer resiliência contra picos repentinos de demanda, dissociando a chegada de tarefas de seu processamento. Ele também pode isolar falhas no processamento da fila para que elas não afetem a ingestão.

- RE:06 Dimensionamento
- RE:07 Trabalhos em segundo plano
A otimização de custos se concentra em sustentar e melhorar o retorno sobre o investimento da sua carga de trabalho. Como o processamento de carga é dissociado da entrada de solicitação ou tarefa, você pode usar essa abordagem para reduzir a necessidade de provisionar recursos em excesso para lidar com o pico de carga.

- CO:12 Custos de dimensionamento
A eficiência de desempenho ajuda sua carga de trabalho a atender com eficiência às demandas por meio de otimizações em dimensionamento, dados e código. Essa abordagem permite o design intencional no desempenho da taxa de transferência, pois a entrada de solicitações não precisa ser correlacionada à taxa em que elas são processadas.

- PE:05 Dimensionamento e particionamento

Tal como acontece com qualquer decisão de design, considere quaisquer compensações em relação aos objetivos dos outros pilares que possam ser introduzidos com este padrão.

Exemplo

Um aplicativo Web grava dados em um repositório de dados externo. Se um grande número de instâncias do aplicativo Web for executado simultaneamente, talvez o armazenamento de dados não consiga responder às solicitações rápido o suficiente, fazendo com que o tempo das solicitações expire, seja limitado ou falhe. O diagrama a seguir mostra um repositório de dados que está sendo sobrecarregado com um grande número de solicitações simultâneas de instâncias de um aplicativo.

Figura 2 - Um serviço que está sendo sobrecarregado com um grande número de solicitações simultâneas de instâncias de um aplicativo Web

Para resolver isso, é possível usar uma fila para nivelar a carga entre as instâncias do aplicativo e o repositório de dados. Um aplicativo do Azure Functions lê as mensagens da fila e executa as solicitações de leitura/gravação para o armazenamento de dados. A lógica do aplicativo no aplicativo de funções pode controlar a taxa em que ele transmite solicitações ao repositório de dados para impedir que o serviço de armazenamento seja sobrecarregado. (Caso contrário, o aplicativo de funções apenas reintroduzirá o mesmo problema no back-end.)

Figura 3 - Como usar uma fila e um aplicativo de funções para nivelar a carga

Próximas etapas

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

  • Prévia de mensagens assíncronas. Filas de mensagens são inerentemente assíncronas. Talvez seja necessário recriar a lógica do aplicativo em uma tarefa se ela tiver sido adaptada de se comunicar diretamente com um serviço para usar uma fila de mensagens. Da mesma forma, talvez seja necessário refatorar um serviço para aceitar solicitações de uma fila de mensagens. Como alternativa, talvez seja possível implementar um serviço de proxy, conforme descrito no exemplo.

  • Escolha entre os serviços de mensagens do Azure. Informações sobre como escolher um mecanismo de mensagens e enfileiramento em aplicativos do Azure.

  • Comunicação assíncrona baseada em mensagens.

  • Estilo de arquitetura Web-Queue-Worker. A Web e o trabalho são sem estado. O estado de sessão pode ser armazenado em um cache distribuído. Qualquer trabalho de longa execução é feito assincronamente pela função de trabalho. A função de trabalho pode ser acionada por mensagens na fila ou executada em um agendamento de processamento em lotes.

Os seguintes padrões também serão relevantes durante a implementação desse padrão:

  • Padrão de consumidores concorrentes. Talvez seja possível executar várias instâncias de um serviço, cada uma agindo como um consumidor de mensagem da fila 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 com um serviço é usar o nivelamento de carga baseado em fila e encaminhar todas as solicitações para um serviço por meio de uma fila de mensagens. O serviço pode processar solicitações a uma taxa que garanta que os recursos exigidos pelo serviço não sejam esgotados e para reduzir a quantidade de contenção que poderia ocorrer.