Padrão de limitação

Limite os recursos que uma instância de aplicativo, um locatário individual ou um serviço inteiro podem consumir. Isso permite que o sistema funcione e atenda aos seus SLOs (objetivos de nível de serviço) sob carga súbita ou sustentada.

Contexto e problema

A carga em um aplicativo de nuvem varia ao longo do tempo com base em usuários ativos e suas atividades. Mais usuários se inscrevem durante o horário comercial e o sistema executa análises computacionalmente caras no final de cada mês. Explosões repentinas também ocorrem. Se a demanda de processamento exceder a capacidade disponível, o sistema diminuirá ou falhará. Quando o sistema tem um nível de serviço acordado, essa falha viola o SLO.

Várias estratégias lidam com cargas variadas, dependendo das metas de negócios do aplicativo. Uma estratégia é o dimensionamento automático, que corresponde aos recursos provisionados ao custo atual de demanda e controles. Mas o provisionamento de novos recursos leva tempo e adiciona custo. A demanda que excede o crescimento da capacidade ou o orçamento cria um déficit de recursos.

Solução

Uma alternativa ao escalonamento automático é limitar o uso de recursos e restringir as solicitações quando o uso ultrapassar esse limite. A carga de trabalho monitora seu próprio uso de recursos e limita as solicitações de um ou mais usuários quando o uso excede o limite. O sistema continua funcionando e atendendo aos seus SLOs.

A limitação de requisições é um ciclo de controle, não uma decisão de admissão única. O sistema precisa de sinais de baixa latência em três camadas: utilização da infraestrutura, estado da aplicação e contadores por principal. Ele mede continuamente a saturação, impõe limites em limites bem definidos e adapta esses limites à medida que os padrões de tráfego mudam. A sobrecarga é um modo operacional normal do qual um sistema maduro detecta e se recupera. A limitação de requisições proporciona autopreservação em sua carga de trabalho.

O sistema pode implementar diversas estratégias de limitação de requisições ou relacionadas:

  • Limites de taxa por principal: Rejeite solicitações de um usuário que já excedeu a taxa configurada dentro de uma janela definida. Esta estratégia exige que o sistema atribua cada requisição a uma entidade principal e meça o uso de recursos em relação a essa entidade. Para cargas de trabalho multilocatários, consulte Medir o consumo de cada locatário.

  • Degradação gradual de funcionalidades: Desative ou degrade funcionalidades não essenciais para que as funcionalidades essenciais disponham de recursos suficientes. Essa estratégia troca a integridade da resposta pela disponibilidade. Por exemplo, um aplicativo de streaming de vídeo pode cair para uma resolução mais baixa.

  • Nivelamento de carga: Suavize o volume de atividade usando uma fila. Em um ambiente multilocatário, o nivelamento reduz o desempenho de cada locatário. Quando os clientes têm diferentes Acordos de Nível de Serviço (SLAs), processe o trabalho para clientes de alto valor imediatamente e suspenda o trabalho de menor prioridade até que o backlog diminua. Implemente essa abordagem usando o padrão fila de prioridade ou expondo pontos de extremidade separados para cada camada de prioridade.

  • Adiamento baseado em prioridade: adie operações em nome de aplicativos ou clientes de menor prioridade. Suspender ou limitar as operações e retornar uma exceção informando ao locatário para tentar novamente mais tarde.

  • Limites de taxa de saída: Restrinja suas próprias chamadas de saída quando uma dependência externa falhar ou retornar erros. Reduza a contagem de requisições em andamento para evitar o excesso de logs e custos de novas tentativas em uma dependência instável. Restaure o fluxo de solicitação normal após a recuperação da dependência. Por exemplo, o NServiceBus implementa essa funcionalidade.

O gráfico a seguir mostra o uso de recursos (uma combinação de memória, CPU, largura de banda e outros fatores) ao longo do tempo para um aplicativo que usa três recursos, rotulados como A, B e C. Um recurso é uma área específica da funcionalidade, como um componente que executa um conjunto específico de tarefas, um pedaço de código que executa um cálculo complexo ou um elemento que fornece um serviço como um cache na memória.

Gráfico que mostra o uso de recursos em relação ao tempo para aplicativos executados em nome de três usuários.

O gráfico é um gráfico de área empilhada. A área abaixo da linha do Recurso A mostra os recursos que o Recurso A consome, a área entre as linhas do Recurso A e do Recurso B mostra os recursos que o Recurso B consome e a área entre as linhas do Recurso B e do Recurso C mostra os recursos que o Recurso C consome. A linha do Recurso C está no topo da pilha, portanto, também mostra o uso total de recursos do sistema ao longo do tempo.

O gráfico mostra a degradação gradual de funcionalidades. Pouco antes do tempo T1, o uso total de recursos se aproxima do limite e corre o risco de esgotar a capacidade disponível. O recurso B é menos crítico que o Recurso A ou o Recurso C, portanto, o sistema desativa o Recurso B e libera seus recursos. Entre os horários T1 e T2, o Recurso A e o Recurso C continuam normalmente. No instante T2, o uso total de recursos cai o suficiente para reativar o Recurso B.

Você pode combinar escalonamento automático, degradação gradual e limitação de taxa para manter os aplicativos responsivos e dentro dos SLAs. Quando você espera que a demanda permaneça alta, a limitação de taxa mantém a estabilidade enquanto o sistema escala horizontalmente. Após a conclusão da escalabilidade, o sistema restaura a funcionalidade completa.

O próximo gráfico mostra o uso total de recursos ao longo do tempo e como a limitação de taxa se combina com o escalonamento automático e outros controles compensatórios.

Gráfico que mostra os efeitos da combinação de limitação de taxa com escalonamento automático.

Um grafo de linha plota a utilização de recursos para todos os aplicativos no eixo y em relação ao tempo no eixo x. Duas linhas de referência horizontais marcam o limite flexível de utilização de recursos e a capacidade máxima antes do dimensionamento automático. Uma linha horizontal mais alta, que começa no momento T2, marca a capacidade máxima após o dimensionamento automático. A linha de utilização sobe e flutua ao longo do tempo. Ele ultrapassa o limite suave no momento T1, que é o ponto em que o dimensionamento automático começa. Entre T1 e T2, o sistema é restringido enquanto ocorre o escalonamento automático, e a utilização permanece abaixo da capacidade máxima anterior ao escalonamento automático. No instante T2, o escalonamento automático é concluído, a limitação de taxa é relaxada e a linha de utilização sobe repentinamente e continua a flutuar abaixo da nova capacidade máxima, mais alta.

No momento T1, o sistema atinge o limite flexível e começa a escalar horizontalmente. Se novos recursos não chegarem a tempo, a demanda poderá esgotar os recursos existentes e o sistema poderá falhar. A limitação de taxa rejeita solicitações em excesso durante a expansão horizontal para manter o uso de recursos abaixo do limite rígido e, em seguida, remove essas restrições após a entrada em operação da nova capacidade.

Dica

Os controles de borda e o padrão de limitação de taxa abordam problemas diferentes. Controles de borda, como o Azure DDoS Protection e as regras de limitação de taxa do WAF (firewall de aplicativo Web), operam na borda da rede e descartam o tráfego volumétrico ou malicioso antes que ele chegue ao seu aplicativo. O padrão Throttling é executado na sua aplicação e controla o tráfego legítimo de acordo com os limites definidos pela aplicação. Use ambas as camadas juntas. A proteção contra DDoS não impede que um usuário legítimo sobrecarregue seu serviço e a limitação do aplicativo não absorve um ataque volumetrico.

Problemas e considerações

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

  • Tome decisões de limitação de taxa antecipadamente. A limitação de taxa é uma decisão arquitetural que afeta todo o sistema. Reajustá-lo mais tarde é caro.

  • Alinhe os limites de limitação de taxa com o componente que satura primeiro.

    A taxa de solicitações é a dimensão mais familiar para limitar, mas o verdadeiro gargalo geralmente são as solicitações simultâneas em andamento, a profundidade da fila, a utilização da CPU ou da memória ou os próprios limites de uma dependência downstream. Um limite de requisições por segundo não protege um sistema cujo gargalo é a concorrência em um ponto de distribuição.

    Em cada limite de imposição de limitação, como o gateway, o serviço, uma partição ou uma dependência downstream, identifique o que satura primeiro e defina o limite nessa dimensão. Para proteção limitada pela concorrência em pontos de distribuição, consulte o padrão Bulkhead, que complementa a limitação.

  • Escolha um algoritmo de limitação intencionalmente. Ajuste-a à tolerância do componente que você está protegendo.

    Algoritmo Comportamento e melhor ajuste
    Bucket de token Suporta picos de requisições até um tamanho configurado e impõe uma taxa de recarga constante. Use-o para gateways que precisam absorver picos de curta duração.
    Bucket com vazamento Emite a uma taxa constante. Use para back-ends que precisam de uma taxa de entrada constante.
    Janela fixa Simples de implementar, mas admite picos consecutivos nos limites da janela.
    Janela Deslizante Suaviza o problema de limite de janela de janelas fixas ao custo de mais estado.
  • Decida quem o limite afeta. A limitação de taxa em um limite grosseiro, como um gateway regional, pode afetar muitos usuários não relacionados quando apenas alguns deles geram a carga.

  • Decida onde o contador reside quando um limite abrange vários nós. Contadores locais são rápidos, mas subestimam a quantidade de solicitações quando o mesmo chamador alcança várias réplicas. Um contador centralizado em um repositório compartilhado como o Redis vê todas as solicitações, mas adiciona latência a cada decisão. Para aproximar uma taxa global, divida o limite entre réplicas e reconcilie periodicamente.

  • Tome decisões de limitação de taxa rapidamente. O sistema deve detectar o aumento da carga, reagir e voltar ao normal após a redução da carga. Esse processo requer instrumentação contínua de desempenho.

  • Reduza a carga de forma proativa, não à beira do colapso. Uma limitação de taxa que só rejeita após a saturação de um componente causa um pico de latência antes que os chamadores percebam qualquer contrapressão.

    À medida que a utilização se aproxima do limite rígido, comece a rejeitar uma fração crescente de solicitações. A rejeição antecipada sinaliza aos chamadores para reduzirem a taxa de solicitação e evita o colapso de latência que limites abruptos costumam desencadear. Use a latência p99 em relação ao seu SLO como o gatilho principal. A utilização média pode parecer saudável mesmo quando o p99 já ultrapassou o limite.

    Quando for possível identificar o valor da solicitação, descarte primeiro as tarefas de menor valor ou mais passíveis de nova tentativa. Para obter mais informações, consulte o padrão de Fila de Prioridade.

  • Retorne um código de status que informe ao cliente quando uma rejeição temporária é resultado da limitação de taxa.

    • HTTP 429 (muitas solicitações): O chamador excede uma taxa de solicitação configurada em uma janela definida.
    • HTTP 503 (Serviço Indisponível): O serviço não pode lidar com a solicitação no momento, muitas vezes devido a um pico de carga inesperado.

    Inclua um cabeçalho HTTP Retry-After para que o cliente possa escolher uma estratégia de nova tentativa. Retorne contexto suficiente para o chamador tentar novamente de forma deliberada em vez de adivinhar. Por exemplo, nomeie o limite que o chamador excede, esclareça o escopo afetado ou sugira uma taxa que tenha êxito. Rejeições inexplicáveis não ajudam os chamadores a se adaptarem.

  • Propague os sinais de sobrecarga de suas dependências em vez de absorvê-los. Um serviço que limita a taxa de suas solicitações também deve respeitar as respostas de limitação de taxa que recebe de suas próprias dependências downstream. Se o seu serviço oculta uma resposta 429 ou 503 de um servidor downstream, tentando novamente silenciosamente ou retornando uma resposta genérica HTTP 500 (Erro Interno do Servidor), os solicitantes não podem reduzir a velocidade, as tentativas se amplificam e a sobrecarga se propaga de volta para os servidores upstream. O antipadrão Retry Storm descreve esse modo de falha. Aplique contrapressão aos solicitantes upstream para que toda a cadeia de chamadas alivie a carga em conjunto.

  • Torne a rejeição mais barata do que o trabalho que ela impede. Se recusar uma solicitação exigir autenticação pesada, análise profunda ou avaliação de política complexa, uma enxurrada de solicitações rejeitadas ainda poderá saturar o sistema. Rejeite o mais cedo possível no pipeline de requisições e teste a carga do próprio caminho de rejeição.

  • Planeje para casos em que a limitação de requisições não seja suficiente para o dimensionamento automático. Se a demanda crescer mais rápido do que a entrada em operação de nova capacidade, mesmo um sistema com capacidade limitada pode falhar. Quando esse resultado for inaceitável, mantenha as reservas de capacidade maiores e configure o dimensionamento automático mais agressivo.

  • Não use cache como substituto para a limitação de requisições. Um cache reduz a carga média na origem, mas não vincula a carga de pico. Cada falha de cache é repassada para a origem e, quando uma chave popular expira sob tráfego intenso, muitos solicitantes podem competir para recarregá-la. Use cache para reduzir a pressão normal e limitação de requisições para limitar o pior caso. Para obter mais informações, consulte o padrão Cache-Aside.

  • Normalize os custos de recursos para operações diferentes porque geralmente não têm custos de execução iguais. Por exemplo, os limites de limitação podem ser maiores para operações de leitura e menores para operações de gravação. Ignorar o custo por operação pode esgotar a capacidade e criar um vetor de ataque.

  • Permita que a configuração de limitação de taxa seja alterada em tempo de execução. Quando uma carga anormal chega, você precisa ajustar os limites sem uma implantação. As implantações são lentas e arriscadas durante um incidente. O padrão do Repositório de Configuração Externo externaliza a configuração para que você possa alterá-la em runtime.

  • Considere limites adaptáveis em vez de limites estáticos. Alguns SDKs de limitação reagem a sinais de latência ou profundidade da fila para que o limite acompanhe as condições reais do componente. Sempre emparelhe um limitador adaptável com um máximo definido.

  • Revisite seus limites à medida que a carga de trabalho evolui. Os limitadores adaptativos não conseguem acompanhar todos os tipos de desvio, como mudanças nos SLOs, mudanças na capacidade das dependências ou alterações no custo por operação. Agende a revisão periódica do operador em relação a essas entradas.

Quando usar esse padrão

Use este padrão:

  • Para manter um sistema dentro de seus SLOs.

  • Para impedir que um único locatário monopolize os recursos do aplicativo.

  • Para lidar com picos de atividade.

  • Para limitar o nível máximo de recurso de que um sistema precisa.

  • Para reduzir cargas de trabalho computacionais de baixo valor em períodos de alta intensidade de carbono na rede elétrica.

Design de carga de trabalho

Avalie como usar o padrão de Limitação no design de uma carga de trabalho para atender às metas e princípios abordados nos pilares da Estrutura bem arquitetada do Azure. 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. Você projeta os limites para ajudar a evitar o esgotamento dos recursos que pode levar ao mau funcionamento. Você também pode usar esse padrão como um mecanismo de controle em uma estratégia de degradação graciosa.

- RE:07 Autopreservação
As decisões de design de segurança ajudam a garantir a confidencialidade, integridade e disponibilidade dos dados e sistemas da sua carga de trabalho. Você pode projetar os limites para ajudar a evitar o esgotamento dos recursos que pode resultar do abuso na automação do sistema.

- SE:06 Controles de rede
- SE:08 Endurecimento de recursos
A Otimização de Custos concentra-se na manutenção e na melhoria do retorno sobre o investimento da carga de trabalho. Os limites impostos podem informar a modelagem de custos e podem ser diretamente vinculados ao modelo de negócios do seu aplicativo. Eles também colocam limites superiores claros na utilização, que podem ser levados em consideração no dimensionamento dos recursos.

- CO:02 Modelo de custos
- 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. Quando o sistema está sob alta demanda, esse padrão ajuda a mitigar o congestionamento que pode causar gargalos de desempenho. Você também pode usá-lo para evitar proativamente cenários de vizinhos barulhentos.

- PE:02 Planejamento de capacidade
- 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

O diagrama a seguir mostra a limitação em um sistema multilocatário.

Diagrama que mostra a limitação em um aplicativo multilocatário.

Três usuários rotulados à esquerda representam locatários de um aplicativo de Pesquisas multilocatário: Adatum, Fabrikam e Contoso. Cada usuário envia solicitações por meio de um domínio personalizado específico do locatário, que o aplicativo usa para identificar o locatário. O Adatum envia 5 solicitações por segundo até surveys.adatum.com, a Fabrikam envia 10 solicitações por segundo até surveys.fabrikam.com e a Contoso envia 150 solicitações por segundo até surveys.contoso.com. À direita, a função web do aplicativo de pesquisas mede a taxa de solicitações por segundo de cada locatário. Os fluxos de requisição da Adatum e da Fabrikam são encaminhados para o aplicativo. O fluxo de solicitações da Contoso é bloqueado por uma resposta de Erro: Limitação, porque a taxa excede o limite por locatário.

Usuários de várias organizações de locatário acessam um aplicativo hospedado na nuvem para preencher e enviar pesquisas. O aplicativo contém instrumentação que monitora a taxa na qual os usuários de cada locatário enviam solicitações.

Para impedir que os usuários de um locatário degradem a capacidade de resposta e a disponibilidade dos usuários em outros locatários, o aplicativo limita a taxa de solicitações por segundo que qualquer locatário pode enviar. O aplicativo bloqueia solicitações que excedem esse limite.

Próxima etapa