Rate limiting e cache
Esta aula aborda a importância do rate limiting e do cache em aplicações PHP, apresentando estratégias práticas e ferramentas. Você aprenderá a implementar controle de acesso, otimizar respostas e melhorar a escalabilidade do seu sistema.
Em aplicações web modernas, a combinação de rate limiting (limitação de taxa) e cache é essencial para garantir desempenho, segurança e estabilidade. O rate limiting protege seus recursos contra abusos, enquanto o cache reduz a carga no servidor e melhora a experiência do usuário. Nesta aula, vamos explorar os conceitos fundamentais, estratégias de implementação e ferramentas que podem ser integradas ao PHP para alcançar esses objetivos.
Vamos começar entendendo por que limitar requisições é importante, depois veremos diferentes estratégias de rate limiting, como implementar cache de respostas e, por fim, uma visão geral das ferramentas disponíveis no ecossistema PHP.
Por que limitar
Rate limiting é a prática de restringir o número de requisições que um cliente pode fazer a um servidor em um determinado período de tempo. Isso é crucial para proteger a infraestrutura, evitar sobrecarga e garantir que todos os usuários tenham acesso justo aos recursos. Sem essa proteção, um único usuário mal-intencionado ou um bug no cliente pode gerar tráfego excessivo, causando indisponibilidade do serviço.
Além da proteção contra ataques de negação de serviço (DDoS), o rate limiting também é usado para cumprir políticas de uso, como limites de API para planos gratuitos ou para evitar scraping agressivo. Em aplicações PHP, podemos implementar desde soluções simples usando armazenamento em arquivo ou banco de dados até soluções robustas com Redis ou bibliotecas especializadas.
Estratégias
Existem várias estratégias para implementar rate limiting. A mais comum é a janela fixa (fixed window), onde definimos um período (ex.: 1 minuto) e um limite de requisições (ex.: 60). O contador é resetado a cada novo período. Essa abordagem é simples, mas pode permitir picos no final de um período e início do próximo.
Outra estratégia é a janela deslizante (sliding window), que considera um histórico contínuo, evitando picos. Há também o token bucket, que permite rajadas controladas, e o leaky bucket, que suaviza a taxa. Vamos ver um exemplo simples de janela fixa usando Redis no PHP.
<?php
// Exemplo de rate limiting com Redis (janela fixa)
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'rate_limit:' . $_SERVER['REMOTE_ADDR'];
$limit = 60; // máximo de requisições
$window = 60; // em segundos
$current = $redis->incr($key);
if ($current == 1) {
$redis->expire($key, $window);
}
if ($current > $limit) {
http_response_code(429);
echo 'Too Many Requests';
exit;
}
?>
Esse código incrementa um contador para o IP do cliente e define uma expiração de 60 segundos. Se o contador exceder o limite, retorna HTTP 429. Em produção, você deve considerar usar um middleware ou biblioteca como symfony/rate-limiter.
Cache de respostas
Cache de respostas é uma técnica que armazena o resultado de uma requisição para reutilizá-lo em requisições futuras, evitando processamento repetido. No PHP, podemos cachear desde páginas inteiras até fragmentos de consultas ao banco de dados. O cache pode ser implementado em vários níveis: no cliente (HTTP cache), no servidor (memória, arquivos) ou em bancos de dados dedicados como Redis ou Memcached.
Para implementar cache em PHP, uma abordagem comum é usar o padrão de cache-aside: verifica se o dado está no cache; se sim, retorna; se não, busca na fonte, armazena no cache e retorna. Vamos ver um exemplo com Redis para cachear o resultado de uma consulta ao banco.
<?php
// Exemplo de cache de resposta com Redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'user:' . $id;
$data = $redis->get($key);
if ($data === false) {
// Consulta ao banco de dados (simulação)
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// Armazena no cache por 10 minutos
$redis->setex($key, 600, json_encode($user));
$data = json_encode($user);
}
echo $data;
?>
Esse exemplo reduz a carga no banco de dados, melhorando a performance. É importante definir uma política de invalidação: quando os dados mudam, o cache deve ser atualizado ou removido. Isso pode ser feito manualmente ou usando técnicas como cache tags.
Visão geral de ferramentas
No ecossistema PHP, existem várias ferramentas que facilitam a implementação de rate limiting e cache. Para cache, temos o APCu (cache em memória local), Redis e Memcached (caches distribuídos) e o OPcache para cache de bytecode. Para rate limiting, bibliotecas como symfony/rate-limiter e league/rate-limit oferecem implementações prontas.
Se você usa um framework como Laravel, ele já inclui suporte a cache e rate limiting prontos para usar. Laravel oferece um sistema de cache unificado e middleware throttle para limitar requisições. Já o Symfony tem componentes de Cache e RateLimiter que podem ser integrados de forma flexível.
Além disso, você pode combinar essas ferramentas com proxies reversos como Nginx ou Varnish, que oferecem cache em nível de servidor e até rate limiting. A escolha da ferramenta depende da complexidade da sua aplicação, da infraestrutura e dos requisitos de escalabilidade.
Boas práticas e observações finais
- Defina limites adequados baseados no comportamento esperado dos usuários.
- Use códigos de status corretos: 429 para rate limit e 304 para cache quando aplicável.
- Inclua cabeçalhos de rate limit como
X-RateLimit-Limitpara informar os clientes. - Invalide o cache sempre que os dados forem alterados.
- Monitore o desempenho do cache e do rate limiting para ajustar parâmetros.
Exercícios
Crie um script PHP que implemente rate limiting usando uma janela fixa com armazenamento em arquivo (não use Redis). Considere que o arquivo pode ser lido e escrito a cada requisição.
✓ Resposta:<?php $ip = $_SERVER['REMOTE_ADDR']; $file = sys_get_temp_dir() . '/rate_' . md5($ip) . '.txt'; $limit = 5; $window = 60; $data = @file_get_contents($file); $data = $data ? json_decode($data, true) : ['count' => 0, 'time' => time()]; if ($data['time'] < time() - $window) { $data = ['count' => 0, 'time' => time()]; } $data['count']++; file_put_contents($file, json_encode($data)); if ($data['count'] > $limit) { http_response_code(429); echo 'Too Many Requests'; exit; } echo 'OK'; ?>Explique a diferença entre cache de página inteira e cache de fragmento. Dê um exemplo de quando usar cada um.
✓ Resposta: O cache de página inteira armazena o HTML completo de uma página, sendo ideal para páginas que não mudam frequentemente por usuário (ex.: página inicial). O cache de fragmento armazena partes da página (ex.: um menu, uma lista de produtos) e é útil quando a página é dinâmica, mas algumas partes são comuns a todos os usuários. Exemplo: cachear o cabeçalho de um site que é igual para todos, mas não cachear o conteúdo personalizado.Implemente um cache de respostas usando APCu para armazenar o resultado de uma função que retorna dados de um arquivo JSON.
✓ Resposta:<?php function getData() { $key = 'data_json'; $data = apcu_fetch($key); if ($data === false) { $data = file_get_contents('data.json'); apcu_store($key, $data, 300); // 5 minutos } return $data; } echo getData(); ?>Descreva como o token bucket funciona e implemente uma versão simples em PHP usando Redis.
✓ Resposta: O token bucket permite rajadas de requisições até um certo limite. Um balde é preenchido com tokens a uma taxa constante; cada requisição remove um token. Se não houver tokens, a requisição é negada. Implementação simples:<?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $key = 'bucket:' . $_SERVER['REMOTE_ADDR']; $rate = 1; // tokens por segundo $burst = 10; // capacidade máxima $tokens = (float)$redis->get($key); if ($tokens === false) { $tokens = $burst; $redis->set($key, $tokens); } if ($tokens > 0) { $tokens--; $redis->set($key, $tokens); echo 'OK'; } else { http_response_code(429); echo 'Too Many Requests'; } ?>Pesquise e liste pelo menos 5 bibliotecas PHP para rate limiting ou cache, com seus respectivos pacotes Composer.
✓ Resposta: Algumas bibliotecas populares:- symfony/rate-limiter
- league/rate-limit
- predis/predis (cliente Redis, usado para cache)
- symfony/cache
- phpfastcache/phpfastcache