Editar

Padrão de limitação de taxa

Azure Service Bus
Azure Queue Storage
Azure Event Hubs

Muitos serviços usam um padrão de limitação para controlar os recursos que consomem, impondo limites à taxa na qual outros aplicativos ou serviços podem acessá-los. Você pode usar um padrão de limitação de taxa para ajudá-lo a evitar ou minimizar erros de limitação relacionados a esses limites de limitação e para ajudá-lo a prever com mais precisão a taxa de transferência.

Um padrão de limitação de taxa é apropriado em muitos cenários, mas é particularmente útil para tarefas automatizadas repetitivas em grande escala, como processamento em lote.

Contexto e problema

Executar um grande número de operações usando um serviço limitado pode resultar em maior tráfego e taxa de transferência, pois você precisará rastrear solicitações rejeitadas e, em seguida, repetir essas operações. À medida que o número de operações aumenta, um limite de limitação pode exigir várias passagens de reenvio de dados, resultando em um impacto maior no desempenho.

Como exemplo, considere a seguinte repetição ingênua no processo de erro para ingerir dados no Azure Cosmos DB:

  1. Seu aplicativo precisa ingerir 10.000 registros no Azure Cosmos DB. Cada registro custa 10 Unidades de Solicitação (RUs) para ingerir, exigindo um total de 100.000 RUs para concluir o trabalho.
  2. Sua instância do Azure Cosmos DB tem 20.000 RUs de capacidade provisionada.
  3. Você envia todos os 10.000 registros para o Azure Cosmos DB. 2.000 registros são gravados com sucesso e 8.000 registros são rejeitados.
  4. Você envia os 8.000 registros restantes para o Azure Cosmos DB. 2.000 registros são escritos com sucesso e 6.000 registros são rejeitados.
  5. Você envia os 6.000 registros restantes para o Azure Cosmos DB. 2.000 registros são escritos com sucesso e 4.000 registros são rejeitados.
  6. Você envia os 4.000 registros restantes para o Azure Cosmos DB. 2.000 registros são gravados com sucesso e 2.000 registros são rejeitados.
  7. Você envia os 2.000 registros restantes para o Azure Cosmos DB. Todos são escritos com sucesso.

O trabalho de ingestão foi concluído com êxito, mas somente após o envio de 30.000 registros para o Azure Cosmos DB, embora todo o conjunto de dados consistisse apenas em 10.000 registros.

Há fatores adicionais a considerar no exemplo acima:

  • Um grande número de erros também pode resultar em trabalho adicional para registrar esses erros e processar os dados de log resultantes. Essa abordagem ingênua terá lidado com 20.000 erros, e o registro desses erros pode impor um custo de recursos de processamento, memória ou armazenamento.
  • Não conhecendo os limites de limitação do serviço de ingestão, a abordagem ingênua não tem como definir expectativas sobre quanto tempo levará o processamento de dados. A limitação da taxa pode permitir calcular o tempo necessário para a ingestão.

Solução

A limitação de taxa pode reduzir seu tráfego e potencialmente melhorar a taxa de transferência, reduzindo o número de registros enviados para um serviço durante um determinado período de tempo.

Um serviço pode ser acelerado com base em diferentes métricas ao longo do tempo, como:

  • O número de operações (por exemplo, 20 solicitações por segundo).
  • A quantidade de dados (por exemplo, 2 GiB por minuto).
  • O custo relativo das operações (por exemplo, 20.000 RUs por segundo).

Independentemente da métrica usada para limitação, sua implementação de limitação de taxa envolverá o controle do número e/ou tamanho das operações enviadas para o serviço durante um período de tempo específico, otimizando seu uso do serviço sem exceder sua capacidade de limitação.

Em cenários em que suas APIs podem lidar com solicitações mais rapidamente do que qualquer serviço de ingestão limitada permite, você precisará gerenciar a rapidez com que pode usar o serviço. No entanto, apenas tratar a limitação como um problema de incompatibilidade de taxa de dados, e simplesmente armazenar em buffer suas solicitações de ingestão até que o serviço limitado possa alcançá-la, é arriscado. Se seu aplicativo falhar nesse cenário, você corre o risco de perder qualquer um desses dados armazenados em buffer.

Para evitar esse risco, considere enviar seus registros para um sistema de mensagens durável que possa lidar com sua taxa de ingestão completa. (Serviços como Hubs de Eventos do Azure podem lidar com milhões de operações por segundo). Em seguida, você pode usar um ou mais processadores de trabalho para ler os registros do sistema de mensagens a uma taxa controlada que esteja dentro dos limites do serviço limitado. O envio de registros para o sistema de mensagens pode salvar a memória interna, permitindo que você remova a fila apenas dos registros que podem ser processados durante um determinado intervalo de tempo.

O Azure fornece vários serviços de mensagens duráveis que você pode usar com esse padrão, incluindo:

Um fluxo de mensagens durável com três processadores de trabalho chamando para um serviço limitado.

Quando você está enviando registros, o período de tempo usado para liberar registros pode ser mais granular do que o período em que o serviço é limitado. Os sistemas geralmente definem os aceleradores com base em intervalos de tempo que você pode facilmente compreender e trabalhar. No entanto, para o computador que executa um serviço, esses prazos podem ser muito longos em comparação com a rapidez com que ele pode processar informações. Por exemplo, um sistema pode acelerar por segundo ou por minuto, mas normalmente o código é processado na ordem de nanossegundos ou milissegundos.

Embora não seja necessário, geralmente é recomendado enviar quantidades menores de registros com mais frequência para melhorar a taxa de transferência. Assim, em vez de tentar agrupar as coisas para uma versão uma vez por segundo ou uma vez por minuto, você pode ser mais granular do que isso para manter seu consumo de recursos (memória, CPU, rede, etc.) fluindo a uma taxa mais uniforme, evitando possíveis gargalos devido a explosões repentinas de solicitações. Por exemplo, se um serviço permite 100 operações por segundo, a implementação de um limitador de taxa pode equilibrar as solicitações liberando 20 operações a cada 200 milissegundos, conforme mostrado no gráfico a seguir.

Um gráfico mostrando a limitação da taxa ao longo do tempo.

Além disso, às vezes é necessário que vários processos descoordenados compartilhem um serviço limitado. Para implementar o limite de taxa nesse cenário, você pode particionar logicamente a capacidade do serviço e, em seguida, usar um sistema de exclusão mútua distribuída para gerenciar bloqueios exclusivos nessas partições. Os processos descoordenados podem então competir por bloqueios nessas partições sempre que precisarem de capacidade. Para cada partição para a qual um processo mantém um bloqueio, é concedida uma certa quantidade de capacidade.

Por exemplo, se o sistema acelerado permitir 500 solicitações por segundo, você poderá criar 20 partições no valor de 25 solicitações por segundo cada. Se um processo precisar emitir 100 solicitações, ele pode solicitar ao sistema de exclusão mútua distribuído quatro partições. O sistema pode conceder duas partições por 10 segundos. O processo classificaria o limite para 50 solicitações por segundo, concluiria a tarefa em dois segundos e, em seguida, liberaria o bloqueio.

Uma maneira de implementar esse padrão seria usar o Armazenamento do Azure. Nesse cenário, você cria um blob de 0 bytes por partição lógica em um contêiner. Seus aplicativos podem então obter locações exclusivas diretamente contra esses blobs por um curto período de tempo (por exemplo, 15 segundos). Para cada locação que um pedido é concedido, ele será capaz de usar o valor da capacidade dessa partição. O aplicativo precisa então controlar o tempo de locação para que, quando expirar, possa parar de usar a capacidade que lhe foi concedida. Ao implementar esse padrão, muitas vezes você desejará que cada processo tente alugar uma partição aleatória quando precisar de capacidade.

Para reduzir ainda mais a latência, você pode alocar uma pequena quantidade de capacidade exclusiva para cada processo. Nesse caso, um processo só procuraria obter um contrato de locação de capacidade partilhada se fosse necessário exceder a sua capacidade reservada.

Partições de Blob do Azure

Como alternativa ao Armazenamento do Azure, você também pode implementar esse tipo de sistema de gerenciamento de locação usando tecnologias como Zookeeper, Consul, etcd, Redis/Redsync e outras.

Problemas e considerações

Considere o seguinte ao decidir como implementar esse padrão:

  • Embora o padrão de limitação de taxa possa reduzir o número de erros de limitação, seu aplicativo ainda precisará lidar corretamente com quaisquer erros de limitação que possam ocorrer.
  • Se seu aplicativo tiver vários fluxos de trabalho que acessam o mesmo serviço limitado, você precisará integrar todos eles à sua estratégia de limitação de taxa. Por exemplo, você pode oferecer suporte ao carregamento em massa de registros em um banco de dados, mas também consultar registros nesse mesmo banco de dados. Você pode gerenciar a capacidade garantindo que todos os fluxos de trabalho sejam fechados por meio do mesmo mecanismo de limitação de taxa. Como alternativa, você pode reservar pools separados de capacidade para cada fluxo de trabalho.
  • O serviço acelerado pode ser usado em vários aplicativos. Em alguns casos, mas não em todos, é possível coordenar esse uso (como mostrado acima). Se você começar a ver um número maior do que o esperado de erros de limitação, isso pode ser um sinal de discórdia entre os aplicativos que acessam um serviço. Nesse caso, talvez seja necessário considerar a redução temporária da taxa de transferência imposta pelo mecanismo de limitação de taxa até que o uso de outros aplicativos diminua.

Quando utilizar este padrão

Utilize este padrão para:

  • Reduza os erros de limitação gerados por um serviço limitado.
  • Reduza o tráfego em comparação com uma tentativa ingênua na abordagem de erro.
  • Reduza o consumo de memória ao enfileirar registros somente quando houver capacidade para processá-los.

Design da carga de trabalho

Um arquiteto deve avaliar como o padrão de Limitação de Taxa 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 suporta os objetivos do pilar
As decisões de projeto 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. Esta tática protege o cliente, reconhecendo e honrando as limitações e os custos da comunicação com um serviço quando o serviço deseja evitar o uso excessivo.

- RE:07 Autopreservação

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

Exemplo

O aplicativo de exemplo a seguir permite que os usuários enviem registros de vários tipos para uma API. Há um processador de trabalho exclusivo para cada tipo de registro que executa as seguintes etapas:

  1. Validação
  2. Melhoramento
  3. Inserção do registo na base de dados

Todos os componentes do aplicativo (API, processador de trabalho A e processador de trabalho B) são processos separados que podem ser dimensionados independentemente. Os processos não se comunicam diretamente entre si.

Um fluxo multifila e multiprocessador com armazenamento particionado para locações, gravando em um banco de dados.

Este diagrama incorpora o seguinte fluxo de trabalho:

  1. Um usuário envia 10.000 registros do tipo A para a API.
  2. A API enfileira esses 10.000 registros na fila A.
  3. Um usuário envia 5.000 registros do tipo B para a API.
  4. A API enfileira esses 5.000 registros na fila B.
  5. O processador de trabalho A vê que a fila A tem registros e tenta obter uma locação exclusiva no blob 2.
  6. O Processador de Trabalho B vê que a Fila B tem registros e tenta obter uma locação exclusiva no blob 2.
  7. O Processador de Trabalho A não consegue obter a concessão.
  8. O Job Processor B obtém a concessão no blob 2 por 15 segundos. Agora ele pode classificar solicitações de limite para o banco de dados a uma taxa de 100 por segundo.
  9. O Processador de Trabalho B retira 100 registros da Fila B e os grava.
  10. Um segundo passa.
  11. O processador de trabalho A vê que a fila A tem mais registros e tenta obter uma locação exclusiva no blob 6.
  12. O processador de trabalho B vê que a fila B tem mais registros e tenta obter uma locação exclusiva no blob 3.
  13. O Processador de Trabalho A obtém a concessão no blob 6 por 15 segundos. Agora ele pode classificar solicitações de limite para o banco de dados a uma taxa de 100 por segundo.
  14. O Job Processor B obtém a concessão no blob 3 por 15 segundos. Agora ele pode classificar solicitações de limite para o banco de dados a uma taxa de 200 por segundo. (Também detém o contrato de arrendamento para o blob 2.)
  15. O Processador de Trabalho A retira 100 registros da Fila A e os grava.
  16. O Processador de Trabalho B retira 200 registros da Fila B e os grava.
  17. Um segundo passa.
  18. O processador de trabalho A vê que a fila A tem mais registros e tenta obter uma locação exclusiva no blob 0.
  19. O processador de trabalho B vê que a fila B tem mais registros e tenta obter uma locação exclusiva no blob 1.
  20. O Processador de Trabalho A obtém a concessão no blob 0 por 15 segundos. Agora ele pode classificar solicitações de limite para o banco de dados a uma taxa de 200 por segundo. (Também detém o contrato de arrendamento para o blob 6.)
  21. O Job Processor B obtém a concessão no blob 1 por 15 segundos. Agora ele pode classificar solicitações de limite para o banco de dados a uma taxa de 300 por segundo. (Também detém o contrato de arrendamento para as bolhas 2 e 3.)
  22. O Processador de Trabalho A retira 200 registros da Fila A e os grava.
  23. O Processador de Trabalho B retira 300 registros da Fila B e os grava.
  24. E assim por diante...

Após 15 segundos, um ou ambos os trabalhos ainda não serão concluídos. À medida que as concessões expiram, um processador também deve reduzir o número de solicitações que desfila e grava.

Logótipo do GitHub Implementações deste padrão estão disponíveis em diferentes linguagens de programação:

  • A implementação Go está disponível no GitHub.
  • A implementação Java está disponível no GitHub.

Os padrões e orientações que se seguem também podem ser relevantes ao implementar este padrão:

  • Limitação. O padrão de limitação de taxa discutido aqui é normalmente implementado em resposta a um serviço que é limitado.
  • Repetir. Quando as solicitações para o serviço limitado resultam em erros de limitação, geralmente é apropriado repeti-los após um intervalo apropriado.

O Nivelamento de Carga Baseado em Fila é semelhante, mas difere do padrão de Limitação de Taxa de várias maneiras principais:

  1. O limite de taxa não precisa necessariamente usar filas para gerenciar a carga, mas precisa fazer uso de um serviço de mensagens durável. Por exemplo, um padrão de limitação de taxa pode usar serviços como Apache Kafka ou Hubs de Eventos do Azure.
  2. O padrão de limitação de taxa introduz o conceito de um sistema de exclusão mútua distribuído em partições, que permite gerenciar a capacidade de vários processos descoordenados que se comunicam com o mesmo serviço limitado.
  3. Um padrão de nivelamento de carga baseado em fila é aplicável sempre que houver uma incompatibilidade de desempenho entre serviços ou para melhorar a resiliência. Isso o torna um padrão mais amplo do que o limite de taxa, que se preocupa mais especificamente com o acesso eficiente a um serviço limitado.