Pergunta

Os usuários legítimos do meu site ocasionalmente martelam o servidor com solicitações de API que causam resultados indesejáveis. Quero instituir um limite não mais do que dizer uma chamada de API a cada 5 segundos ou n chamadas por minuto (ainda não descobri o limite exato). Obviamente, eu poderia registrar todas as chamadas da API em um banco de dados e fazer o cálculo de todas as solicitações para ver se elas estão acima do limite, mas toda essa sobrecarga extra em todas as solicitações estaria derrotando o objetivo. Quais são outros métodos menos intensivos em recursos que eu poderia usar para instituir um limite? Estou usando o PHP/APACHE/Linux, pelo que vale a pena.

Foi útil?

Solução

Ok, não há como fazer o que eu pedi sem algum grava no servidor, mas posso pelo menos eliminar o registro de todas as solicitações. Uma maneira é usar o método de limpeza "com vazamento", onde ele apenas acompanha a última solicitação ($last_api_request) e uma proporção do número de solicitações/limite para o prazo ($minute_throttle). O balde de vazamento nunca redefine seu contador (ao contrário do acelerador da API do Twitter que redefine a cada hora), mas se o balde ficar cheio (o usuário atingiu o limite), eles devem esperar n segundos para o balde esvaziar um pouco antes de poder fazer outra solicitação. Em outras palavras, é como um limite de rolamento: se houver solicitações anteriores dentro do prazo, elas estão lentamente vazando para fora do balde; Isso só o restringe se você preencher o balde.

Este snippet de código calculará um novo $minute_throttle valor em cada solicitação. Eu especifiquei o minuto dentro $minute_throttle Porque você pode adicionar aceleração para qualquer período de tempo, como a cada hora, diariamente, etc ... embora mais de um comece rapidamente a torná -lo confuso para os usuários.

$minute = 60;
$minute_limit = 100; # users are limited to 100 requests/minute
$last_api_request = $this->get_last_api_request(); # get from the DB; in epoch seconds
$last_api_diff = time() - $last_api_request; # in seconds
$minute_throttle = $this->get_throttle_minute(); # get from the DB
if ( is_null( $minute_limit ) ) {
    $new_minute_throttle = 0;
} else {
    $new_minute_throttle = $minute_throttle - $last_api_diff;
    $new_minute_throttle = $new_minute_throttle < 0 ? 0 : $new_minute_throttle;
    $new_minute_throttle += $minute / $minute_limit;
    $minute_hits_remaining = floor( ( $minute - $new_minute_throttle ) * $minute_limit / $minute  );
    # can output this value with the request if desired:
    $minute_hits_remaining = $minute_hits_remaining >= 0 ? $minute_hits_remaining : 0;
}

if ( $new_minute_throttle > $minute ) {
    $wait = ceil( $new_minute_throttle - $minute );
    usleep( 250000 );
    throw new My_Exception ( 'The one-minute API limit of ' . $minute_limit 
        . ' requests has been exceeded. Please wait ' . $wait . ' seconds before attempting again.' );
}
# Save the values back to the database.
$this->save_last_api_request( time() );
$this->save_throttle_minute( $new_minute_throttle );

Outras dicas

Você pode controlar a taxa com o Algoritmo de balde de token, que é comparável ao algoritmo de balde com vazamento. Observe que você terá que compartilhar o estado do balde (ou seja, a quantidade de tokens) sobre os processos (ou qualquer escopo que você queira controlar). Portanto, você pode pensar em travar para evitar condições de corrida.

A boa notícia: eu fiz tudo isso para você: Largura de banda-arremesso/token-bucket

use bandwidthThrottle\tokenBucket\Rate;
use bandwidthThrottle\tokenBucket\TokenBucket;
use bandwidthThrottle\tokenBucket\storage\FileStorage;

$storage = new FileStorage(__DIR__ . "/api.bucket");
$rate    = new Rate(10, Rate::SECOND);
$bucket  = new TokenBucket(10, $rate, $storage);
$bucket->bootstrap(10);

if (!$bucket->consume(1, $seconds)) {
    http_response_code(429);
    header(sprintf("Retry-After: %d", floor($seconds)));
    exit();
}

Não sei se esse tópico ainda está vivo ou não, mas sugiro manter essas estatísticas no cache de memória como o memcached. Isso reduzirá a sobrecarga de registrar a solicitação ao banco de dados, mas ainda servirá ao objetivo.

A solução mais simples seria apenas dar a cada chave da API um número limitado de solicitações por 24 horas e redefini -las em algum tempo conhecido, fixo.

Se eles esgotarem suas solicitações de API (ou seja, o contador atingir zero ou o limite, dependendo da direção que você conta), pare de servir os dados até redefinir o contador deles.

Dessa forma, estará em seus O melhor interesse em não martelar você com solicitações.

Você diz que "toda a sobrecarga extra em cada solicitação seria derrotar o objetivo", mas não tenho certeza de que isso esteja correto. Não é o objetivo de impedir a martelamento do seu servidor? Provavelmente é assim que eu o implementaria, pois realmente requer apenas uma rápida leitura/gravação. Você pode até cultivar as verificações do servidor da API em um banco de dados/disco diferente se estivesse preocupado com o desempenho.

No entanto, se você quiser alternativas, você deve conferir mod_cband, um módulo Apache de terceiros projetado para ajudar no acelerador de largura de banda. Apesar de ser principalmente para limitação de largura de banda, ela pode acelerar com base em solicitações por segundo também. Eu nunca usei, então não tenho certeza de que tipo de resultado você obteria. Havia outro módulo chamado Mod-Oftle também, mas esse projeto parece estar fechado agora e nunca foi lançado para nada acima da série Apache 1.3.

Além da implementação do zero, você também pode dar uma olhada na infraestrutura da API como 3Scale (http://www.3scale.net) que avalia limitantes, bem como um monte de outras coisas (análises etc.). Há um plugin PHP para ele: https://github.com/3scale/3scale_ws_api_for_php.

Você também pode colocar algo como verniz na frente da API e fazer a taxa de API limitando assim.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top